Reference

Payment webhook events: a reference

What each event class means, why ordering is never guaranteed, and the five properties any webhook consumer needs before it can be trusted with money.

A blueprint schematic in mint line-work on deep violet: a provider box sending a stream of webhook envelopes to a consumer box, one refund envelope lifted above the line and annotated as arriving late, with a dimension line reading event stream, eight classes.

Webhooks are how a payment system tells you something happened. They are also delivered at least once, in no guaranteed order, over a channel that can be down — which means a consumer written naively will eventually produce a duplicate payout or a wrong balance.

This is the event classes, and the five properties a consumer needs before it can be trusted with money.

The event classes

Names vary by provider; the classes do not.

ClassTypical eventsWhat it means for your records
Payment intent / authorisationcreated · requires action · succeeded · failed · cancelledThe customer’s payment attempt. Only succeeded is money.
Charge / capturesucceeded · failed · captured · expiredMoney actually taken. This is the ledger event.
Refundcreated · succeeded · failedReverses a charge, often later and partially
Dispute / chargebackopened · evidence required · won · lostMoney removed pending outcome; deadlines attached
Payout / transfercreated · paid · failed · returnedMoney leaving to a bank account. failed can arrive days later.
Settlement / balanceavailable · reserved · settlement createdAggregation events — the ones reconciliation depends on
Account / onboardingupdated · requirements due · restrictedCapability changes; blocks payouts silently
Mandate / subscriptioncreated · updated · cancelled · payment dueRecurring authorisation state

Two classes get under-handled and both cost money:

  • Account events. A connected account becoming restricted stops payouts without producing a payout failure. Nothing looks broken until someone asks where their money is.
  • Settlement events. These carry the composition of an aggregated deposit, which is exactly what reconciliation needs and what is otherwise only available by export.

The five properties a consumer needs

1 · Signature verification, always

Verify before parsing. The signature proves the message came from the sender and was not altered in transit.

What it does not prove: that the payload is still accurate. The object may have changed between the event being generated and your processing it.

2 · Idempotency

Delivery is at least once, not exactly once.

The usual cause of a duplicate is benign: your endpoint processed the request successfully and responded too slowly, the sender treated it as failed, and retried. You now have the same event twice.

Store the event identifier, check it before processing, discard repeats. Without this, a duplicate delivery becomes a duplicate ledger entry — or a duplicate payout.

3 · Order independence

Ordering is never guaranteed. Events travel as independent HTTP requests. A retry of an earlier event can arrive after a later one. Two events generated milliseconds apart can arrive either way round.

The correct pattern: treat each event as something changed, then resolve current state — from a version or timestamp on the event, or by fetching the object. Never as this happened next.

The classic failure: handling payout.paid and payout.failed in arrival order. Arriving reversed, the payout is recorded as paid.

4 · Fast acknowledgement, separate processing

Acknowledge immediately, queue the work.

Endpoints that do their processing inline eventually exceed the sender’s timeout under load, which produces retries, which produce more load. The failure mode is a cascade, and it happens at the busiest moment.

5 · A path that does not depend on webhooks

If your endpoint is down, you get a gap, and the gap is silent. Providers retry on backoff for hours or days, then stop, and often disable the endpoint.

Any system depending on webhooks for financial state needs a periodic sweep: list objects changed since a timestamp, compare against what you recorded, fill differences. Run it daily. It also catches the events you handled wrongly rather than missed, which the webhook channel cannot tell you about.

Which events are money

Not every event is a ledger event, and treating them as such is how balances go wrong.

EventLedger entry?
Payment intent createdNo
Payment intent succeededUsually — check capture semantics
Charge capturedYes
Refund succeededYes
Dispute openedYes — funds withdrawn pending outcome
Dispute wonYes — funds returned
Payout createdNo
Payout paidYes — bank movement
Payout failed / returnedYes — reverses the above
Account updatedNo

The authorisation-versus-capture distinction is where this most often goes wrong. An authorisation reserves; a capture takes. Posting revenue on authorisation overstates it and requires an unwind when the authorisation expires.

A handling checklist

  • Signature verified before parsing
  • Event id stored; repeats discarded
  • State resolved by version or fetch, never by arrival order
  • Acknowledged within the sender’s timeout, processed asynchronously
  • Financial state re-fetched from the API, not taken from the payload
  • Daily sweep comparing recorded state against the provider
  • Account and settlement events handled, not just payment ones
  • Unknown event types logged rather than rejected
  • Failure to process alerts a person, rather than returning an error and relying on retries

The last two matter more than they look. Providers add event types, and an endpoint that errors on an unrecognised type turns a new feature into an outage.

Why this ends up in reconciliation

Webhooks are usually the only place the composition of a settlement is available in real time.

A settlement arrives at your bank as one line. The webhook stream knows which charges, refunds and fees composed it. A consumer that handles payment events and skips settlement events has all the pieces and none of the assembly — which is exactly the position that makes month end an export-and-match exercise.

Where Fynex fits

Fynex keeps the composition rather than requiring you to rebuild it from an event stream.

Payments carry what they settle, so the relationship between a charge, the invoice it clears, the fee deducted and the settlement it arrives in is held as a record rather than assembled from events that may arrive out of order or not at all. Failures and returns reopen what they reverse at the moment they arrive.

Where you are consuming webhooks today, the checklist above is the right shape, and the daily sweep is the item worth adding first — it is the only one that catches what the channel silently lost.

Related: Payment decline codes · Pending, processing, settled, cleared · settlement · Reconciliation

FAQ

Frequently asked questions

No, and building as though you can is the most common and most expensive webhook mistake. Events are delivered over independent HTTP requests, so a retry of an earlier event can arrive after a later one, and two events generated milliseconds apart can arrive in either order. The correct approach is to treat each event as a signal that something changed and to resolve the current state — either from a timestamp or version on the event itself, or by fetching the object — rather than assuming the sequence of arrivals reflects the sequence of occurrences.
An idempotency key is a stable identifier that lets a receiver recognise it has already processed a given message. Webhooks are delivered at least once, not exactly once, so the same event will sometimes arrive twice — typically when your endpoint processed a request successfully but responded slowly enough that the sender treated it as failed and retried. Storing processed event identifiers and discarding repeats is what prevents a duplicate delivery from becoming a duplicate ledger entry or a duplicate payout.
Verify the signature always, and for anything that moves money or posts an entry, re-fetch the object from the API rather than acting on the payload alone. The signature proves the message came from the sender and was not altered. It does not prove the payload is still current, since the object may have changed between the event being generated and your processing it. For low-consequence updates the payload is fine; for financial state, fetch.
Most providers retry on a backoff schedule for a period measured in hours or days, then stop and often disable the endpoint. That means a sustained outage produces a gap, and the gap is silent — nothing tells you which events you missed. Any system that depends on webhooks for financial state needs a reconciliation path that does not depend on webhooks, typically a periodic sweep listing objects changed since a timestamp and comparing against what was recorded.
Book a demo