diff --git a/payments/webhook-event-reference.mdx b/payments/webhook-event-reference.mdx
new file mode 100644
index 0000000..cbe473e
--- /dev/null
+++ b/payments/webhook-event-reference.mdx
@@ -0,0 +1,537 @@
+---
+title: "Webhook event reference"
+description: "Field-level reference for every payment webhook event: the envelope, the payment snapshot, each stage object, and a full example payload per event type."
+sidebarTitle: "Webhook events"
+---
+
+This page is the field-level reference for the payment webhook payload, `api_version` `2026-05-18`. It documents every field, its type and nullability, and which stage objects are populated for each event type. For endpoint setup, signature verification, and delivery guarantees, see [Webhooks](/payments/webhooks).
+
+
+**The contract is additive-only.** Within this `api_version`, fields are never removed or renamed, and types and nullability never change. New **optional** fields and new values for extensible strings (such as `fee.kind`) can be added at any time. Ignore fields you don't recognize, and never validate payloads with a schema that rejects unknown fields; strict validation will break your integration.
+
+
+All timestamps in the payload are ISO 8601 UTC strings.
+
+## The event envelope
+
+Every event has the same top-level shape.
+
+| Field | Type | Description |
+|---|---|---|
+| `id` | string | Unique event identifier, prefixed `evt_`. The deduplication key: delivery is at-least-once, so the same `id` can arrive more than once. Do not deduplicate by `payment_id`; one payment emits multiple events. |
+| `type` | string | The event type. One of the seven [event types](#event-types) below. |
+| `api_version` | string | The payload contract version, `2026-05-18`. Changes within a version are additive-only. |
+| `created_at` | string | When the event occurred, not when it was delivered. |
+| `data` | object | The [payment snapshot](#the-payment-snapshot-data) at event time. |
+
+## The payment snapshot (`data`)
+
+`data` is always a **full snapshot of the payment at the moment the event occurred**, never a delta. Every event type carries the same field set; what varies is `status` and which stage objects are non-null.
+
+| Field | Type | Description |
+|---|---|---|
+| `payment_id` | string | Payment identifier, the same id used across the Merchant API. |
+| `merchant_id` | string | Merchant identifier, the same external merchant id used across the Merchant API. |
+| `live` | boolean | Whether this is a live payment. Test-mode payments carry `false` and are only delivered to test-mode endpoints. Verify `live` is `true` before driving real fulfillment. |
+| `payment_state_version` | integer | Monotonic per-payment version, starting at `0` on the first event. The ordering guard: ignore any event whose version is lower than or equal to one already processed for the same `payment_id`. See [Out-of-order delivery](/payments/webhooks#out-of-order-delivery). |
+| `reference_id` | string | The merchant-provided order reference attached at payment creation. Primary reconciliation key against your own records. Not unique: a new payment for the same order (for example a checkout retry) carries the same reference. |
+| `status` | string | Payment status at event time: `requires_action`, `processing`, `succeeded`, `failed`, `expired`, or `cancelled`. There is no `settled` status; settlement is a stage of a succeeded payment. |
+| `amount` | [Amount](#amount) | The requested payment amount. |
+| `created_at` | string | When the payment was created. |
+| `expires_at` | string | When the payment stops accepting buyer action. |
+| `processing` | [processing](#processing) \| null | Populated once the buyer has committed to a payment option. |
+| `success` | [success](#success) \| null | Populated once the payment succeeded. |
+| `failed` | [failed](#failed) \| null | Populated once the payment failed. |
+| `cancelled` | [cancelled](#cancelled) \| null | Populated once the payment was cancelled. |
+| `expired` | [expired](#expired) \| null | Populated once the payment expired. |
+| `settled` | [settled](#settled) \| null | Populated once merchant settlement completed. |
+
+### Stage objects by event type
+
+Which stage objects are non-null depends on the event type.
+
+| Event type | `status` | `processing` | `success` | `failed` | `cancelled` | `expired` | `settled` |
+|---|---|---|---|---|---|---|---|
+| `payment.created` | `requires_action` | null | null | null | null | null | null |
+| `payment.processing` | `processing` | present | null | null | null | null | null |
+| `payment.succeeded` | `succeeded` | present | present | null | null | null | usually null |
+| `payment.failed` | `failed` | may be present | null | present | null | null | null |
+| `payment.expired` | `expired` | may be present | null | null | null | present | null |
+| `payment.cancelled` | `cancelled` | null | null | null | present | null | null |
+| `payment.settled` | `succeeded` | present | present | null | null | null | present |
+
+"May be present" means the payment can fail or expire either before or after processing started: `processing` is `null` when the buyer never committed to a payment option, and populated otherwise. Cancellation is only possible while the payment awaits buyer action, so `payment.cancelled` always carries `processing: null`. On `payment.succeeded`, `settled` is `null` unless settlement had already completed at snapshot time; settlement completion is carried by `payment.settled`.
+
+## Objects
+
+### Amount
+
+An asset amount. Used for `data.amount` and inside the [processing](#processing), [fee](#fee), and [settled](#settled) objects.
+
+| Field | Type | Description |
+|---|---|---|
+| `unit` | string | The asset: `iso4217/XXX` for fiat currencies (for example `iso4217/USD`) or `caip19/...` for on-chain assets. |
+| `value` | string | Integer string in the smallest unit of the asset: minor units for fiat, raw base units for tokens. Token decimals are not included in the payload. |
+
+### fee
+
+Fee applied to the payment, inside [processing](#processing).
+
+| Field | Type | Description |
+|---|---|---|
+| `kind` | string | Extensible string. The only value at launch is `base_plus_percent`. Do not switch exhaustively on it; new kinds can be added within this `api_version`. Whatever the kind, `total_amount` is always present. |
+| `base_amount` | [Amount](#amount) | The fixed component of the fee. |
+| `percent` | object | The percentage as an exact rational: `numerator` and `denominator`, both integer strings. For example `29/1000` is 2.9%. |
+| `percent_amount` | [Amount](#amount) | The percentage component of the fee. |
+| `total_amount` | [Amount](#amount) | The total fee charged. `base_amount` and `percent_amount` break it down. |
+
+### processing
+
+Present once the buyer has committed to a payment option.
+
+| Field | Type | Description |
+|---|---|---|
+| `processing_at` | string | When the buyer committed to the payment option. |
+| `option_amount` | [Amount](#amount) | What the buyer pays gross, in the asset of the payment option they selected. |
+| `fee` | [fee](#fee) \| null | Fee applied to this payment, or `null` when no fee is configured. |
+| `settlement_amount` | [Amount](#amount) | What the merchant is expected to receive, net of `fee`. |
+| `buyer_caip10` | string \| null | The buyer's on-chain account ([CAIP-10](https://chainagnostic.org/CAIPs/caip-10)), or `null` when the payment has no on-chain buyer (for example, payments settled inside an exchange). |
+| `chain_caip2` | string | The chain the payment executes on ([CAIP-2](https://chainagnostic.org/CAIPs/caip-2)). Needed to interpret transaction ids. |
+
+### success
+
+Present once the payment succeeded.
+
+| Field | Type | Description |
+|---|---|---|
+| `succeeded_at` | string | When the payment succeeded. |
+| `tx_id` | string \| null | On-chain transaction id, or `null` when there is no on-chain transaction. Format is chain-specific; interpret with `processing.chain_caip2`. |
+
+### failed
+
+Present once the payment failed.
+
+| Field | Type | Description |
+|---|---|---|
+| `failed_at` | string | When the payment failed. |
+| `failure_reason` | string \| null | Human-readable diagnostic, or `null`. Not machine-parseable: values may change without notice. Display it if useful, but never branch on it. |
+
+### cancelled
+
+Present once the payment was cancelled.
+
+| Field | Type | Description |
+|---|---|---|
+| `cancelled_at` | string | When the payment was cancelled. |
+| `cancellation_reason` | string \| null | Human-readable diagnostic, or `null`. Not machine-parseable: values may change without notice. Display it if useful, but never branch on it. |
+
+### expired
+
+Present once the payment expired.
+
+| Field | Type | Description |
+|---|---|---|
+| `expired_at` | string | When the payment expired. |
+
+### settled
+
+Present once merchant settlement completed.
+
+| Field | Type | Description |
+|---|---|---|
+| `settled_at` | string | When settlement completed. |
+| `tx_id` | string \| null | Settlement transaction id, or `null` when there is no on-chain settlement transaction. Format is chain-specific; interpret with `processing.chain_caip2`. |
+| `amount` | [Amount](#amount) | What actually settled to the merchant, as opposed to `processing.settlement_amount`, which is the expected net amount at processing time. |
+
+## Event types
+
+One example per event type. The examples are the golden fixtures the payload contract is tested against.
+
+### payment.created
+
+A payment was created and awaits buyer action. Initial snapshot: no lifecycle stage has happened yet, so every stage object is `null` and `payment_state_version` is `0`.
+
+```json payment.created expandable
+{
+ "id": "evt_fixture_payment_created",
+ "type": "payment.created",
+ "api_version": "2026-05-18",
+ "created_at": "2026-06-30T09:59:00.084Z",
+ "data": {
+ "payment_id": "pay_fixture_01HZX4V9K3T",
+ "merchant_id": "merchant_fixture_01HZX4V9K3T",
+ "live": true,
+ "payment_state_version": 0,
+ "reference_id": "order_fixture_1042",
+ "status": "requires_action",
+ "amount": {
+ "unit": "iso4217/USD",
+ "value": "1000"
+ },
+ "created_at": "2026-06-30T09:59:00.084Z",
+ "expires_at": "2026-06-30T10:14:00.000Z",
+ "processing": null,
+ "success": null,
+ "failed": null,
+ "cancelled": null,
+ "expired": null,
+ "settled": null
+ }
+}
+```
+
+### payment.processing
+
+The buyer committed to a payment option and the payment started processing. `processing` is populated from this point on.
+
+In this example the buyer pays 10 USDC on Base for a $10.00 payment, a 2.9% fee applies, and the merchant is expected to receive 9.71 USDC. `processing.buyer_caip10` is `null` instead when the payment has no on-chain buyer, and `live` is `false` for test-mode payments.
+
+```json payment.processing expandable
+{
+ "id": "evt_fixture_payment_processing",
+ "type": "payment.processing",
+ "api_version": "2026-05-18",
+ "created_at": "2026-06-30T09:59:10.512Z",
+ "data": {
+ "payment_id": "pay_fixture_01HZX4V9K3T",
+ "merchant_id": "merchant_fixture_01HZX4V9K3T",
+ "live": true,
+ "payment_state_version": 1,
+ "reference_id": "order_fixture_1042",
+ "status": "processing",
+ "amount": {
+ "unit": "iso4217/USD",
+ "value": "1000"
+ },
+ "created_at": "2026-06-30T09:59:00.084Z",
+ "expires_at": "2026-06-30T10:14:00.000Z",
+ "processing": {
+ "processing_at": "2026-06-30T09:59:10.512Z",
+ "option_amount": {
+ "unit": "caip19/eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "value": "10000000"
+ },
+ "fee": {
+ "kind": "base_plus_percent",
+ "base_amount": {
+ "unit": "caip19/eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "value": "0"
+ },
+ "percent": {
+ "numerator": "29",
+ "denominator": "1000"
+ },
+ "percent_amount": {
+ "unit": "caip19/eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "value": "290000"
+ },
+ "total_amount": {
+ "unit": "caip19/eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "value": "290000"
+ }
+ },
+ "settlement_amount": {
+ "unit": "caip19/eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "value": "9710000"
+ },
+ "buyer_caip10": "eip155:8453:0x000000000000000000000000000000000000dEaD",
+ "chain_caip2": "eip155:8453"
+ },
+ "success": null,
+ "failed": null,
+ "cancelled": null,
+ "expired": null,
+ "settled": null
+ }
+}
+```
+
+### payment.succeeded
+
+The payment succeeded. `success` is populated, and `processing` carries the committed option. `settled` is usually `null` here; settlement completion is carried by [`payment.settled`](#payment-settled). It may be non-null when settlement had already completed at snapshot time.
+
+```json payment.succeeded expandable
+{
+ "id": "evt_fixture_payment_succeeded",
+ "type": "payment.succeeded",
+ "api_version": "2026-05-18",
+ "created_at": "2026-06-30T10:00:00.271Z",
+ "data": {
+ "payment_id": "pay_fixture_01HZX4V9K3T",
+ "merchant_id": "merchant_fixture_01HZX4V9K3T",
+ "live": true,
+ "payment_state_version": 2,
+ "reference_id": "order_fixture_1042",
+ "status": "succeeded",
+ "amount": {
+ "unit": "iso4217/USD",
+ "value": "1000"
+ },
+ "created_at": "2026-06-30T09:59:00.084Z",
+ "expires_at": "2026-06-30T10:14:00.000Z",
+ "processing": {
+ "processing_at": "2026-06-30T09:59:10.512Z",
+ "option_amount": {
+ "unit": "caip19/eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "value": "10000000"
+ },
+ "fee": {
+ "kind": "base_plus_percent",
+ "base_amount": {
+ "unit": "caip19/eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "value": "0"
+ },
+ "percent": {
+ "numerator": "29",
+ "denominator": "1000"
+ },
+ "percent_amount": {
+ "unit": "caip19/eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "value": "290000"
+ },
+ "total_amount": {
+ "unit": "caip19/eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "value": "290000"
+ }
+ },
+ "settlement_amount": {
+ "unit": "caip19/eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "value": "9710000"
+ },
+ "buyer_caip10": "eip155:8453:0x000000000000000000000000000000000000dEaD",
+ "chain_caip2": "eip155:8453"
+ },
+ "success": {
+ "succeeded_at": "2026-06-30T10:00:00.271Z",
+ "tx_id": "0x8f1e5c1b9a4a1d2e3f405162738495a6b7c8d9e0f1a2b3c4d5e6f708192a3b4c"
+ },
+ "failed": null,
+ "cancelled": null,
+ "expired": null,
+ "settled": null
+ }
+}
+```
+
+### payment.failed
+
+The payment failed. `failed` is populated. `processing` is `null` when the payment failed before the buyer committed, as in this example, and populated when the payment had started processing before failing.
+
+```json payment.failed expandable
+{
+ "id": "evt_fixture_payment_failed",
+ "type": "payment.failed",
+ "api_version": "2026-05-18",
+ "created_at": "2026-06-30T10:00:00.271Z",
+ "data": {
+ "payment_id": "pay_fixture_01HZX4V9K3T",
+ "merchant_id": "merchant_fixture_01HZX4V9K3T",
+ "live": true,
+ "payment_state_version": 1,
+ "reference_id": "order_fixture_1042",
+ "status": "failed",
+ "amount": {
+ "unit": "iso4217/USD",
+ "value": "1000"
+ },
+ "created_at": "2026-06-30T09:59:00.084Z",
+ "expires_at": "2026-06-30T10:14:00.000Z",
+ "processing": null,
+ "success": null,
+ "failed": {
+ "failed_at": "2026-06-30T10:00:00.271Z",
+ "failure_reason": "insufficient allowance"
+ },
+ "cancelled": null,
+ "expired": null,
+ "settled": null
+ }
+}
+```
+
+### payment.expired
+
+The payment expired before completing. `expired` is populated. A payment can expire after processing started, as in this example; `processing` is `null` when it expired while still awaiting buyer action.
+
+```json payment.expired expandable
+{
+ "id": "evt_fixture_payment_expired_after_processing",
+ "type": "payment.expired",
+ "api_version": "2026-05-18",
+ "created_at": "2026-06-30T10:14:00.030Z",
+ "data": {
+ "payment_id": "pay_fixture_01HZX4V9K3T",
+ "merchant_id": "merchant_fixture_01HZX4V9K3T",
+ "live": true,
+ "payment_state_version": 2,
+ "reference_id": "order_fixture_1042",
+ "status": "expired",
+ "amount": {
+ "unit": "iso4217/USD",
+ "value": "1000"
+ },
+ "created_at": "2026-06-30T09:59:00.084Z",
+ "expires_at": "2026-06-30T10:14:00.000Z",
+ "processing": {
+ "processing_at": "2026-06-30T09:59:10.512Z",
+ "option_amount": {
+ "unit": "caip19/eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "value": "10000000"
+ },
+ "fee": {
+ "kind": "base_plus_percent",
+ "base_amount": {
+ "unit": "caip19/eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "value": "0"
+ },
+ "percent": {
+ "numerator": "29",
+ "denominator": "1000"
+ },
+ "percent_amount": {
+ "unit": "caip19/eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "value": "290000"
+ },
+ "total_amount": {
+ "unit": "caip19/eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "value": "290000"
+ }
+ },
+ "settlement_amount": {
+ "unit": "caip19/eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "value": "9710000"
+ },
+ "buyer_caip10": "eip155:8453:0x000000000000000000000000000000000000dEaD",
+ "chain_caip2": "eip155:8453"
+ },
+ "success": null,
+ "failed": null,
+ "cancelled": null,
+ "expired": {
+ "expired_at": "2026-06-30T10:14:00.030Z"
+ },
+ "settled": null
+ }
+}
+```
+
+### payment.cancelled
+
+The payment was cancelled. `cancelled` is populated. A payment can only be cancelled while it awaits buyer action, so `processing` is always `null`.
+
+```json payment.cancelled expandable
+{
+ "id": "evt_fixture_payment_cancelled",
+ "type": "payment.cancelled",
+ "api_version": "2026-05-18",
+ "created_at": "2026-06-30T10:00:00.271Z",
+ "data": {
+ "payment_id": "pay_fixture_01HZX4V9K3T",
+ "merchant_id": "merchant_fixture_01HZX4V9K3T",
+ "live": true,
+ "payment_state_version": 1,
+ "reference_id": "order_fixture_1042",
+ "status": "cancelled",
+ "amount": {
+ "unit": "iso4217/USD",
+ "value": "1000"
+ },
+ "created_at": "2026-06-30T09:59:00.084Z",
+ "expires_at": "2026-06-30T10:14:00.000Z",
+ "processing": null,
+ "success": null,
+ "failed": null,
+ "cancelled": {
+ "cancelled_at": "2026-06-30T10:00:00.271Z",
+ "cancellation_reason": "merchant cancelled"
+ },
+ "expired": null,
+ "settled": null
+ }
+}
+```
+
+### payment.settled
+
+Merchant settlement completed for a succeeded payment. `settled` is populated, and `status` stays `succeeded`; settlement is a stage, not a status. `settled.amount` is what actually settled to the merchant.
+
+```json payment.settled expandable
+{
+ "id": "evt_fixture_payment_settled",
+ "type": "payment.settled",
+ "api_version": "2026-05-18",
+ "created_at": "2026-06-30T10:05:00.696Z",
+ "data": {
+ "payment_id": "pay_fixture_01HZX4V9K3T",
+ "merchant_id": "merchant_fixture_01HZX4V9K3T",
+ "live": true,
+ "payment_state_version": 3,
+ "reference_id": "order_fixture_1042",
+ "status": "succeeded",
+ "amount": {
+ "unit": "iso4217/USD",
+ "value": "1000"
+ },
+ "created_at": "2026-06-30T09:59:00.084Z",
+ "expires_at": "2026-06-30T10:14:00.000Z",
+ "processing": {
+ "processing_at": "2026-06-30T09:59:10.512Z",
+ "option_amount": {
+ "unit": "caip19/eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "value": "10000000"
+ },
+ "fee": {
+ "kind": "base_plus_percent",
+ "base_amount": {
+ "unit": "caip19/eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "value": "0"
+ },
+ "percent": {
+ "numerator": "29",
+ "denominator": "1000"
+ },
+ "percent_amount": {
+ "unit": "caip19/eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "value": "290000"
+ },
+ "total_amount": {
+ "unit": "caip19/eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "value": "290000"
+ }
+ },
+ "settlement_amount": {
+ "unit": "caip19/eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "value": "9710000"
+ },
+ "buyer_caip10": "eip155:8453:0x000000000000000000000000000000000000dEaD",
+ "chain_caip2": "eip155:8453"
+ },
+ "success": {
+ "succeeded_at": "2026-06-30T10:00:00.271Z",
+ "tx_id": "0x8f1e5c1b9a4a1d2e3f405162738495a6b7c8d9e0f1a2b3c4d5e6f708192a3b4c"
+ },
+ "failed": null,
+ "cancelled": null,
+ "expired": null,
+ "settled": {
+ "settled_at": "2026-06-30T10:05:00.696Z",
+ "tx_id": "0x93d2f1a0b9c8e7d6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2",
+ "amount": {
+ "unit": "caip19/eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "value": "9710000"
+ }
+ }
+ }
+}
+```
+
+## Next steps
+
+
+
+ Endpoint setup, signature verification, and delivery guarantees.
+
+
+ Drive a test payment through every transition and watch these events arrive.
+
+
diff --git a/payments/webhooks.mdx b/payments/webhooks.mdx
index a8c6cc6..7f65906 100644
--- a/payments/webhooks.mdx
+++ b/payments/webhooks.mdx
@@ -68,7 +68,7 @@ The fields inside `data` you'll use most:
| `payment_state_version` | Monotonically increasing integer per payment. **Your ordering key**; see [Out-of-order delivery](#out-of-order-delivery). |
| `live` | `false` for test-mode payments and test deliveries. **Check `live === true` before driving real fulfillment.** |
| `amount` | The requested amount: `{ "unit": "iso4217/USD", "value": "1000" }`, where `value` is an integer string in the smallest unit. |
-| `processing`, `success`, `failed`, `cancelled`, `expired`, `settled` | Stage detail blocks, `null` until the payment reaches that stage. `success` and `settled` include `tx_id` (chain-specific transaction id, may be `null`); `failed` and `cancelled` include human-readable `failure_reason` / `cancellation_reason`. |
+| `processing`, `success`, `failed`, `cancelled`, `expired`, `settled` | Stage detail blocks, `null` until the payment reaches that stage. Every field, with types and a full example per event type: [Webhook event reference](/payments/webhook-event-reference). |
A `payment.succeeded` event looks like this:
@@ -522,6 +522,9 @@ Webhook configuration does **not** carry over from test to live; they are separa
## Next steps
+
+ Every field of every event type, with types, nullability, and examples.
+
Simulate every payment status transition and watch the webhooks arrive.