Event reference
Every webhook event XPay can deliver, when it fires, and the object it carries.
Events are how XPay tells your server something happened: a payment succeeded, a refund landed, a customer was created. Every event delivered to your endpoint shares the same JSON envelope; what changes is the value of type and the resource carried inside data.object.
This page is the canonical list of events you can subscribe to. For the click path to subscribe, see Setting up an endpoint. For the verifier code, see Verifying signatures.
The event envelope
Every webhook delivery has the same top-level shape:
{
"id": "evt_test_AbC123...",
"object": "event",
"api_version": "3.1.0",
"created": "2026-05-01T12:00:00.000Z",
"type": "checkout.session.completed",
"livemode": false,
"data": {
"object": { "id": "cs_test_...", "object": "checkout.session" }
}
}| Field | Type | Meaning |
|---|---|---|
id | string | Event ID, prefixed evt_test_* or evt_live_*. Stable across retries. Use as your idempotency key. |
object | string | Always "event". |
api_version | string | API version used to render data.object. |
created | ISO 8601 string | When the event was first recorded. |
type | string | What kind of event (see the tables below). |
livemode | boolean | true for live-mode events, false for test-mode. |
data.object | object | The resource the event is about. Identical shape to GET /<resource>/:id. |
The data.object is identical to what the corresponding GET endpoint returns. If you can read a Checkout Session from GET /checkout/sessions/:id, your checkout.session.completed handler can read the same fields by the same names off data.object.
Checkout Session events
Fire on the lifecycle of a Checkout SessionAPI. These are the events most integrations key off.
| Event | Fires when | data.object |
|---|---|---|
checkout.session.completed | A customer's payment on the session succeeds. The session's status is now complete. | Checkout SessionAPI |
Subscribe to checkout.session.completed to fulfill the order. The payload carries the resolved paymentIntent, customer, and lineItems, so a single event is enough for most fulfillment logic.
Charge events
Fire on the lifecycle of a ChargeAPI. A Charge is one attempt to move money on the customer's payment method. A successful payment produces one Charge; a customer who retried after a decline produces several.
| Event | Fires when | data.object |
|---|---|---|
charge.succeeded | A Charge captures successfully. Fires before checkout.session.completed for hosted-checkout payments. | ChargeAPI |
charge.failed | A Charge attempt fails. Carries failureCode and failureMessage describing why. | ChargeAPI |
charge.refunded | A successful Refund (full or partial) is applied to the Charge. The refunded amount is in amountRefunded. The Charge's refunds.data array carries every Refund applied so far. | ChargeAPI |
Use charge.* events when your data model tracks money at the charge level (e.g. multiple captures, retried payments). For most integrations, listening to checkout.session.completed and refund.* is enough.
Refund events
Fire on the lifecycle of a RefundAPI.
| Event | Fires when | data.object |
|---|---|---|
refund.created | A new Refund record is created. | RefundAPI |
refund.failed | A Refund attempt fails. The Refund's status is failed. | RefundAPI |
Both API-issued and dashboard-issued refunds emit these. See Refunds for the API path.
charge.refunded (above) carries the parent Charge with the new Refund nested inside refunds.data, so you can key fulfillment off either side depending on which entity your data model tracks.
Customer events
Fire on the lifecycle of a CustomerAPI.
| Event | Fires when | data.object |
|---|---|---|
customer.created | A Customer record is created (via the API or the dashboard). | CustomerAPI |
customer.updated | A Customer record's properties change. | CustomerAPI |
customer.deleted | A Customer record is deleted. | CustomerAPI |
For the difference between Registered and Guest customers, and how guest matching works, see Customer lifecycle.
Webhook monitoring
One event fires when a webhook itself fails. Useful for paging yourself when production deliveries break.
| Event | Fires when |
|---|---|
webhook_endpoint.delivery_failed | A webhook delivery exhausts all retries. The first transition to failed for a given delivery emits one event. |
Subscribe on a separate, healthy endpoint (a Slack relay, a status-page hook, an internal monitoring URL). The data.object carries:
id: the failed delivery's ID.url: the endpoint URL that failed.eventType: the event that couldn't be delivered.webhookEndpointId: the failing endpoint's ID.attemptCount: how many attempts were tried.lastAttemptStatus: the last HTTP status returned, or0for network failures.lastAttemptError: the error message from the last attempt.
Pair with Replaying & retries if you want to manually replay events once the underlying handler is fixed.
Order on a successful payment
A single payment fires more than one event. They arrive close together but not necessarily in order; treat each event as independent and dedup on event.id.
| Order | Event | Why |
|---|---|---|
| 1 | charge.succeeded | The money moved. |
| 2 | checkout.session.completed | The session is now complete. Fulfillment trigger. |
For a refund:
| Order | Event | Why |
|---|---|---|
| 1 | refund.created | A Refund record was created. |
| 2 | charge.refunded | The parent Charge's amountRefunded was updated. |
You don't need to listen to all of them. Pick the events your data model needs and ignore the rest.
Reading data.object
The data.object field is the same shape you'd get from the matching GET endpoint. Don't reach for the API to re-fetch a resource right after receiving its event; the payload already has everything that endpoint would return.
For the canonical shape of each resource, see the API Reference Object pages:
For how those resources fit together (and which IDs to keep on your order record), see Object model.
Idempotency reminder
Every event can be delivered more than once: XPay retries on non-2xx, you may manually replay from the Workbench, and a successful response that didn't make it back to XPay produces a duplicate. Use event.id as the dedup key in your handler.
For the full pattern, see Verifying signatures → Idempotency.
Where to next
Setting up an endpoint
Add an endpoint, pick events, copy the signing secret.
Verifying signatures
The HMAC-SHA256 verifier and idempotency pattern.
Replaying and retries
Automatic retry schedule, manual resends, and the lifecycle of a delivery.
Local development
Tunnel deliveries to your laptop while you build the handler.
Object model
How Checkout Session, Payment Intent, Charge, Refund, and Customer relate.
Local development
Tunnel webhook deliveries to your laptop while you build. Build, fix, replay, repeat.
Introduction
XPay surfaces failures in two distinct places: the response body of an API call you made, and the lastPaymentError field on a payment that failed. Pick the surface that matches your problem.