Around 30.3 million people are paid through PAYE Real Time Information in the UK each month, and every payment carries a Full Payment Submission that must reach HMRC on or before payday [1] [2]. That submission is not instant. HMRC validates it in stages and returns an acknowledgement that a client application has to wait for, which is exactly the kind of delayed outcome a webhook was designed to communicate [3].
This article is written for developers and product teams. It explains what a payroll webhook is, why payroll and Real Time Information are asynchronous by nature, which events a payroll engine emits, and how to consume a webhook safely with signature verification, idempotency and sensible retry handling.
A payroll webhook is an HTTP callback the engine sends to the host platform when something happens: a payrun finishes calculating, an RTI submission is accepted or rejected, or a payslip becomes available. Instead of the host platform polling an endpoint to ask "is it done yet", the engine pushes the event the moment it occurs. For a process where a filing outcome can arrive seconds or minutes after the request, that difference is the difference between a responsive product and a spinning progress bar [4].
Key takeaways
- A payroll webhook is an HTTP callback the engine sends when a payrun, submission or payslip event occurs, so the host platform does not have to poll [4].
- Real Time Information is asynchronous: HMRC validates a submission in stages and returns an acknowledgement the client must wait for [3].
- A consumer must verify the webhook signature before parsing the payload, to prove the request came from the engine [5].
- Webhooks are delivered at least once, so processing must be idempotent to avoid paying an employee or filing twice [6].
- A late Full Payment Submission can trigger a fixed penalty from £100 to £400 depending on scheme size, which is why the submission outcome is worth a first-class event [7].
What a payroll webhook is
A webhook inverts the usual request and response. In a normal API call the host platform asks a question and waits for the answer in the same connection. A webhook lets the engine start a new connection later, to the host platform, carrying an event that has just happened. The host platform registers a URL once, and the engine posts a JSON payload to that URL each time a relevant event occurs [4].
This matters in payroll because many outcomes are not known at the moment of the request. A payrun for a large employer takes time to calculate, and an RTI submission has to pass through HMRC's validation before it is accepted or rejected [8]. Without webhooks, the host platform has to poll repeatedly and guess when to stop. With webhooks, the engine simply tells the host the moment the outcome is known.
Webhooks versus polling
Polling and webhooks solve the same problem in opposite directions. Polling has the host platform call an endpoint on a timer until the status changes, which wastes requests and adds latency between the event and the host learning about it. Webhooks push the event once, when it happens, so the host learns immediately and makes no wasted calls [6].
HMRC's own systems show both models in use. The legacy RTI channel expects a client to submit, then issue follow-on poll messages after a delay specified in the acknowledgement, while HMRC's more recent Push Pull Notifications service creates notifications in response to asynchronous events and lets a client either receive them or poll no more than once every 10 seconds [3] [4]. A payroll engine that exposes webhooks gives the host platform the push model without having to implement the polling itself. Moonworkers surfaces these events through its HMRC-recognised payroll API.
Why payroll is asynchronous
The case for payroll webhooks rests on the fact that the two most important payroll outcomes, a completed payrun and an accepted submission, are not available synchronously. Understanding the asynchronous shape of Real Time Information is what makes the event model obvious.
RTI validation happens in stages
An RTI submission is validated in stages, and an acknowledgement report is generated after each stage to indicate acceptance or rejection [3]. The client application waits for the interval specified in the acknowledgement, then checks progress, because the outcome of the data and cross-field checks is not returned in the initial response [8]. The submission can be accepted, or it can be rejected for a validation error such as an invalid National Insurance number or a malformed tax code, and the host platform needs to know which [9].
A webhook is the natural way to carry that result. The engine submits to HMRC, waits for the acknowledgement, and then emits a single event, `submission.accepted` or `submission.rejected`, to the host platform. The host never has to know that a poll loop happened underneath; it simply receives the outcome.
The cost of missing the outcome
The submission outcome is not a cosmetic detail, because a Full Payment Submission that misses payday can attract a penalty. The fixed late filing penalty depends on the number of employees in the scheme, as set out below.
| Employees in PAYE scheme | Fixed monthly penalty |
|---|---|
| 1 to 9 | £100 |
| 10 to 49 | £200 |
| 50 to 249 | £300 |
| 250 or more | £400 |
Sources: HMRC compliance handbook and late-filing guidance [7] [10].
There is a three-day easement, so a return sent within three days of the payment date does not create a late-filing failure, and the first late filing in a tax year is not penalised [11]. After three months, an extended failure attracts a further penalty of 5% of the tax and National Insurance that would have been shown on the missing return [12]. A rejected submission that the host platform never learns about can quietly become a penalty, which is why a reliable `submission.rejected` webhook is a compliance safeguard, not a convenience. Larger employers running high volumes through an enterprise payroll integration feel this most.
The events a payroll engine emits
A useful payroll webhook catalogue mirrors the lifecycle of a payrun. The host platform subscribes to the events it cares about and ignores the rest. The table below sets out the common events and what each one signals.
| Event | Fires when | Typical host action |
|---|---|---|
| `payrun.completed` | A payrun finishes calculating | Show net pay, unlock approval |
| `submission.accepted` | HMRC accepts the FPS or EPS | Mark the period as filed |
| `submission.rejected` | HMRC rejects a submission | Surface the error, block payday |
| `payslip.available` | A payslip PDF is ready | Deliver or display the payslip |
| `employee.updated` | A tax code or details change | Refresh the employee record |
Sources: RTI submission types and the engine event model [8] [2].
Payload design
A webhook payload should carry enough to identify the event and little else. A good payload includes a unique event identifier, an event type, a timestamp, and a reference to the resource that changed, so the consumer can fetch the full resource over the API if it needs the detail [6]. Keeping the payload lean reduces the blast radius if a payload is ever exposed, and it avoids the host platform acting on stale embedded data when the authoritative record lives behind the API.
Tax code changes are events too
Not every payroll event is a submission. HMRC issues tax code changes that an employer must apply, and a payroll engine can emit an `employee.updated` event when a code changes so the host platform refreshes its own copy. This keeps the host's employee record in step with the engine without a nightly reconciliation job, and it matters because applying an out-of-date tax code produces the wrong deduction on the next payrun [13]. A payroll REST API that pairs resources with events lets the host stay in sync by reacting rather than polling.
Consuming a webhook safely
Receiving a webhook is easy; receiving one safely takes three disciplines: verify the signature, make processing idempotent, and respond quickly while working in the background.
Verify the signature first
A webhook endpoint is a public URL, so the consumer must prove each request genuinely came from the engine. The standard method is a signature: the engine computes a hash of the raw payload using a shared secret and sends it in a header, and the consumer computes the same hash and compares the two using a constant-time comparison [14]. The signature check must come first, before the JSON is parsed or any work is queued, because an unverified payload should never reach the rest of the handler [6].
Signatures also defend against replay. A signed timestamp in the header lets the consumer reject a captured request that is re-sent later, with a tolerance window of around five minutes a common default, and server clocks kept in sync so a legitimate event is not rejected [5]. Together, signature and timestamp verification stop a forged or replayed webhook from triggering a payday action.
Make processing idempotent
Webhooks are delivered at least once, which means the same event can arrive more than once, so processing has to be idempotent [6]. The standard pattern is to store the unique event identifier and check it before acting, so a duplicate `submission.accepted` does not mark a period filed twice and a repeated `payrun.completed` does not trigger a second payment [14]. In payroll the cost of a non-idempotent handler is not a duplicate log line, it is a duplicate payment or a double filing, so the idempotency key is a safety control rather than an optimisation.
Respond fast, work in the background
A consumer should acknowledge a webhook immediately with an HTTP 200 and do the real work asynchronously, queuing the payload for a background worker rather than processing it inside the request [6]. This keeps the endpoint fast and lets the engine consider the event delivered, which stops unnecessary retries. When the engine does retry a failed delivery, it should back off progressively, the same pattern HMRC recommends for its own rate-limited responses, where a client captures the throttling signal and retries after a short randomised delay rather than hammering the endpoint [15]. Bureaux managing many client schemes through a multi-client payroll platform rely on this back-off to keep a busy payday from overwhelming their webhook consumer.
Recognition and the wider integration
A payroll engine that emits these events must still meet the baseline every UK payroll product meets. Software that files RTI has to be HMRC-recognised, which certifies that it meets HMRC's specification for sending Full Payment Submissions and other RTI messages, and recognised products appear on the GOV.UK register [16] [17]. For a platform choosing an engine to embed, recognition is a given, so the decision turns on how cleanly the events map to the host product and how robust the delivery guarantees are.
Webhooks are one half of a pair. The API lets the host platform act on payroll, and the webhook lets the engine tell the host when something has happened. A design that treats resources and events as two sides of the same integration, documented together in a public reference like the Moonworkers API documentation, is what lets a host platform run compliant UK payroll inside its own product without building either the tax logic or the polling machinery. The mechanics of the underlying filing are covered in the Moonworkers guide to the RTI submission API.
Conclusion
Payroll webhooks exist because the outcomes that matter most in payroll arrive late. A payrun takes time to calculate, and an RTI submission is validated in stages before HMRC accepts or rejects it, so the host platform needs a way to learn the result the moment it is known rather than polling and guessing. A webhook is that mechanism, and a rejected-submission event in particular is a compliance safeguard against a penalty that starts at £100 and climbs with the size of the scheme.
The direction of travel is towards event-driven payroll, where the host platform reacts to what the engine tells it rather than reconciling on a schedule. The engines that make this reliable are the ones that sign every payload, guarantee at-least-once delivery, and document their events beside their endpoints, so the host platform can consume them with signature checks, idempotency keys and a background queue as the default shape of the integration.
Frequently asked questions
Why does a payroll platform need webhooks rather than just an API?
Because the most important payroll outcomes are asynchronous. A payrun takes time to calculate, and an RTI submission is validated by HMRC in stages before it is accepted or rejected, so the result is not available in the original request [3]. A webhook lets the engine push the outcome the moment it is known, which removes the wasteful polling a host platform would otherwise have to build [4].
How should a consumer verify a payroll webhook?
The consumer should verify the signature before parsing the payload. The engine sends a hash of the raw payload computed with a shared secret, and the consumer recomputes it and compares using a constant-time comparison [14]. A signed timestamp lets the consumer reject replayed requests outside a short tolerance window, commonly around five minutes [5]. Only after the signature passes should the handler process the event.
What happens if the same webhook is delivered twice?
Webhooks are delivered at least once, so duplicates are expected and processing must be idempotent [6]. Storing the unique event identifier and checking it before acting means a repeated event is recognised and ignored, so a duplicate `payrun.completed` does not trigger a second payment and a repeated `submission.accepted` does not file a period twice [14]. In payroll this idempotency is a safety control, because the cost of acting twice is a duplicate payment.
Which payroll events are worth subscribing to?
The events that mirror the payrun lifecycle are the most valuable: `payrun.completed` when calculation finishes, `submission.accepted` and `submission.rejected` for the RTI outcome, and `payslip.available` when a statement is ready [8]. The rejected-submission event is the most important for compliance, because a Full Payment Submission that misses payday can attract a fixed penalty from £100 to £400 depending on scheme size [7]. Subscribing to it lets the host platform catch a problem before it becomes a penalty.



