sequence and applicationId.
The envelope
The body is byte-for-byte identical across every delivery and replay of the same event. Anything that varies per delivery attempt — the delivery ID, attempt count, timestamp, signature — lives in the HTTP headers instead.
Events, deliveries, and attempts
Three distinct records stand behind every webhook that reaches you, and each carries its own identifier and its own timestamps. Keeping them straight is the key to reconciling the various timestamps and thesequence logic below.
The split follows one rule: anything intrinsic to the event —
id, type, sequence, originalCreateDate, data — lives in the body and never changes; anything describing this particular HTTP request — delivery ID, attempt number, attempt type, timestamp, signature — lives in the headers and is regenerated per attempt.
Payloads carry the state change, not the full resource
Webhook payloads deliberately contain the minimum you need to react: the resource ID, and the transition frompreviousStatus to status. They do not contain the full resource object.
Most reactions — routing on the new status, updating your own database row, notifying your operations team — work from the payload alone. When you need the complete resource (full verification criteria, bank account details, transfer amounts), fetch it from the resource’s GET endpoint:
The sequence field
sequence is a monotonically increasing integer scoped to a single resource instance — one counter per customer, one per transfer, and so on, keyed by data.id. It is assigned when the event is created and never changes, so it reflects the true order in which state changes occurred even when deliveries arrive out of order.
Because delivery order is not guaranteed, use sequence to guard against stale updates:
- Store the highest
sequenceyou have processed per resource instance — for example, alastSphereSequencecolumn on the customer row in your own database. - When an event arrives, compare its
sequenceto the stored value for thatdata.id. - If
incoming.sequence <= stored.sequence, the event is stale or a duplicate — acknowledge with a2xxand skip the update. - Otherwise, apply the update and store the new
sequence.
Do not assume sequence values you receive are contiguous. If your endpoint subscribes to a subset of a resource’s events, you will observe gaps — use the
<= staleness comparison above, never gap detection.Why both sequence and originalCreateDate?
They answer different questions. originalCreateDate tells you when a state change happened — wall-clock time, useful for display, audit trails, and time-window queries. But wall-clock time is an unreliable ordering key: two rapid transitions on the same resource can land close enough together that their timestamps collide, and a timestamp comparison has no way to break the tie. sequence exists to make ordering unambiguous — a strictly increasing integer per resource instance, so any two events for the same resource always have a definite order, no matter how close together they occurred.
Rule of thumb: order and deduplicate by sequence; display and audit by originalCreateDate.
The applicationId field
Every payload includes data.applicationId — the SpherePay application the event belongs to. Webhook endpoints are registered per application, and events are only ever delivered for resources owned by the endpoint’s application.
This matters most when you operate multiple SpherePay applications that share one webhook receiver URL. Registering the same URL under two applications is perfectly valid — but your handler then receives events from both, and each application’s endpoint has its own signing secret. Use applicationId to route each event to the right application context in your system (and to select the right secret when verifying signatures).