> For the complete index of the 402pay docs, see [llms.txt](https://developer.402pay.co/llms.txt).

# Webhook event catalog

Every event type, what its data holds, and the order a payment's events arrive in.

Every change in your business is recorded as an event, and each webhook endpoint receives the types it subscribes to. [`GET /events`](https://developer.402pay.co/api/events/list.md) lists the same events any time. This page shows what an event holds, every type, and the order they arrive in.

## The envelope

Every event has the same fields around its `data`, whether it arrives as a webhook or from `GET /events`.

Event, Real:

```json
{
  "id": "evt_JxmQNXpkZZN8mpqE",
  "kind": "event",
  "type": "payment.succeeded",
  "subject": {
    "kind": "payment",
    "id": "pmt_QI02vLdJGd48hBbg"
  },
  "data": {
    "id": "pmt_QI02vLdJGd48hBbg",
    "kind": "payment",
    "status": "succeeded",
    "amount": 4900,
    "currency": "USD",
    "fee_payer": "business",
    "customer_fee": 0,
    "amount_received": 4900,
    "reporting": {
      "currency": "USD",
      "amount": 4900,
      "fee": 0,
      "transaction_fee": 25,
      "customer_fee": 0,
      "net": 4875
    },
    "fee_rate_bps": 0,
    "method": {
      "rail": "crypto",
      "asset": "USDC",
      "network": "polygon",
      "amount": "49.00",
      "expected_amount": "49.00",
      "overpaid_amount": null,
      "from_address": "0x136e44a2738dea5217e8d6745463d2a9a84d1421",
      "tx_hash": "0xc16a611d90302ba917593ed9743f5e734a556e8d2f35bb0d60a2ca02ac99bb20",
      "explorer_url": "https://polygonscan.com/tx/0xc16a611d90302ba917593ed9743f5e734a556e8d2f35bb0d60a2ca02ac99bb20"
    },
    "settlement": {
      "destination": "wallet",
      "asset": "USDC",
      "network": "polygon",
      "amount": "49.00",
      "wallet_id": "wal_1fIGZOrILmsCO0jw",
      "address": "0x9a3c416D4A81669C01f4515bBDEB93C45d3813fe",
      "tx_hash": "0xc16a611d90302ba917593ed9743f5e734a556e8d2f35bb0d60a2ca02ac99bb20",
      "explorer_url": "https://polygonscan.com/tx/0xc16a611d90302ba917593ed9743f5e734a556e8d2f35bb0d60a2ca02ac99bb20"
    },
    "customer_id": "cst_gWFWcc7Ga0Pv7LSC",
    "link_id": null,
    "url": "https://checkout.402pay.co/checkout/2jrsrcxv7k",
    "reference": "order_1042",
    "metadata": {
      "order_id": "1042"
    },
    "success_url": "https://example.com/thanks",
    "cancel_url": "https://example.com/cart",
    "expires_at": "2026-09-27T21:22:47.038Z",
    "canceled_at": null,
    "checkout_id": "chk_C5yhPQvPqpYgdDgj",
    "description": "Pro plan, monthly",
    "country": "US",
    "failure_code": null,
    "failure_message": null,
    "confirmed_at": "2026-09-26T21:23:13.447Z",
    "created_at": "2026-09-26T21:22:47.038Z",
    "updated_at": "2026-09-26T21:23:13.447Z",
    "events": [
      {
        "type": "created",
        "created_at": "2026-09-26T21:22:47.038Z",
        "data": null
      },
      {
        "type": "method_selected",
        "created_at": "2026-09-26T21:22:47.047Z",
        "data": {
          "rail": "crypto",
          "asset": "USDC",
          "network": "polygon"
        }
      },
      {
        "type": "detected",
        "created_at": "2026-09-26T21:23:07.047Z",
        "data": {
          "amount": "49.00",
          "network": "polygon",
          "tx_hash": "0xc16a611d90302ba917593ed9743f5e734a556e8d2f35bb0d60a2ca02ac99bb20"
        }
      },
      {
        "type": "confirmation",
        "created_at": "2026-09-26T21:23:08.647Z",
        "data": {
          "current": 1,
          "required": 4
        }
      },
      {
        "type": "confirmation",
        "created_at": "2026-09-26T21:23:10.247Z",
        "data": {
          "current": 2,
          "required": 4
        }
      },
      {
        "type": "confirmation",
        "created_at": "2026-09-26T21:23:11.847Z",
        "data": {
          "current": 3,
          "required": 4
        }
      },
      {
        "type": "confirmation",
        "created_at": "2026-09-26T21:23:13.447Z",
        "data": {
          "current": 4,
          "required": 4
        }
      },
      {
        "type": "succeeded",
        "created_at": "2026-09-26T21:23:13.447Z",
        "data": null
      }
    ]
  },
  "actor": {
    "kind": "system",
    "id": null,
    "name": null
  },
  "mode": "live",
  "api_version": "v1",
  "created_at": "2026-09-26T21:23:15.256Z"
}
```

Event, Test:

```json
{
  "id": "evt_AncdRQ0YlMsNNIQD",
  "kind": "event",
  "type": "payment.succeeded",
  "subject": {
    "kind": "payment",
    "id": "pmt_test_wcKbRA2NP8gXhlmr"
  },
  "data": {
    "id": "pmt_test_wcKbRA2NP8gXhlmr"
  },
  "actor": {
    "kind": "system",
    "id": null,
    "name": null
  },
  "mode": "live",
  "test": true,
  "api_version": "v1",
  "created_at": "2026-09-26T21:23:17.117Z"
}
```

| Field | Meaning |
| --- | --- |
| `id` | The event's ID, which is also the `webhook-id` header of every delivery of it. |
| `kind` | Always `event`. |
| `type` | What happened, such as `payment.succeeded`. The catalog below lists them all. |
| `subject` | The `kind` and `id` of the object it's about. |
| `data` | A snapshot of the subject when the event fired. See below. |
| `actor` | Who caused it: `user`, `api_key`, `customer` or `system`, with an `id` and `name` where there is one. A customer's `name` is their email, when checkout had one. |
| `mode` | `test` or `live`. Always `live` during the beta. |
| `api_version` | The version of the event's shape, `v1` today. |
| `test` | Only on test deliveries, and then `true`. |
| `created_at` | When it happened. |

## What data holds

`data` is the subject as the API returns it elsewhere, frozen when the event fired. A delivery sends those exact bytes every time, resends included, so fetch the object again when you need its state now.

| subject.kind | data |
| --- | --- |
| `payment` | The payment, as [`GET /payments/{id}`](https://developer.402pay.co/api/payments/retrieve.md) returns it, timeline included. |
| `link` | The [link](https://developer.402pay.co/api/links/retrieve.md), with its payment count and volume. |
| `customer` | The [customer](https://developer.402pay.co/api/customers/retrieve.md), with their stats. |
| `wallet` | Only `id`, `kind`, `name` and `created_at`. Balances and keys are never included. |
| `wallet_transaction` | The [wallet transaction](https://developer.402pay.co/api/wallet/transactions/retrieve.md). |
| `api_key` | The key with its name, mode and permissions. Its secret is redacted, never sent. |
| `webhook` | The [endpoint](https://developer.402pay.co/api/webhooks/list.md) with its `secret_hint`. The signing secret is never sent. |
| `business` | The [business](https://developer.402pay.co/api/businesses/retrieve.md) profile. |
| `checkout_settings` | Your checkout settings. `subject.id` is the business's ID, and `data` has no ID of its own. |
| `referral` | A business you referred, as your Partners page shows it: its name, `status`, when it joined and what you've earned from it. Nothing else about the other business is included. |
| `referral_payout` | A month of referral earnings: its `period`, `amount`, and the wallet address and `tx_hash` it was sent with. |
| `fee` | A [fee entry](https://developer.402pay.co/api/billing/fees/entries.md) of type `collection`, with the `balance` you owed right after it. |

Snapshots, Link:

```json
{
  "id": "evt_Lq3VnB8xR2mK7tYc",
  "kind": "event",
  "type": "link.created",
  "subject": {
    "kind": "link",
    "id": "lnk_nt842tPNne3lf0ka"
  },
  "data": {
    "id": "lnk_nt842tPNne3lf0ka",
    "kind": "link",
    "code": "v6e4mrczwg",
    "url": "https://checkout.402pay.co/checkout/v6e4mrczwg",
    "name": "Team plan, monthly",
    "description": "Everything in Pro for up to 10 people.",
    "amount": 19900,
    "currency": "USD",
    "success_url": null,
    "status": "active",
    "disabled_at": null,
    "payments_count": 0,
    "volume": {
      "amount": 0,
      "currency": "USD"
    },
    "created_at": "2026-09-26T21:22:46.992Z",
    "updated_at": "2026-09-26T21:22:46.992Z"
  },
  "actor": {
    "kind": "api_key",
    "id": "key_7fQm2VxL0cR9tB4n",
    "name": "Order server"
  },
  "mode": "live",
  "api_version": "v1",
  "created_at": "2026-09-26T21:22:46.992Z"
}
```

Snapshots, Customer:

```json
{
  "id": "evt_Cw9HsE4pZ1uN6jXa",
  "kind": "event",
  "type": "customer.created",
  "subject": {
    "kind": "customer",
    "id": "cst_gWFWcc7Ga0Pv7LSC"
  },
  "data": {
    "id": "cst_gWFWcc7Ga0Pv7LSC",
    "kind": "customer",
    "name": "Harper Wilson",
    "email": "harper.wilson@example.com",
    "blocked": false,
    "note": "",
    "stats": {
      "payments_count": 2,
      "incomplete_count": 2,
      "volume": {
        "amount": 22810,
        "currency": "USD"
      },
      "average": {
        "amount": 11405,
        "currency": "USD"
      },
      "last_payment_at": "2026-09-26T21:22:47.082Z",
      "preferred_rail": "crypto"
    },
    "created_at": "2026-09-26T21:01:16.809Z",
    "updated_at": "2026-09-26T21:01:16.809Z"
  },
  "actor": {
    "kind": "customer",
    "id": null,
    "name": "harper.wilson@example.com"
  },
  "mode": "live",
  "api_version": "v1",
  "created_at": "2026-09-26T21:01:16.809Z"
}
```

Snapshots, Wallet:

```json
{
  "id": "evt_Wr5TgM0kD8bQ2vLe",
  "kind": "event",
  "type": "wallet.created",
  "subject": {
    "kind": "wallet",
    "id": "wal_1fIGZOrILmsCO0jw"
  },
  "data": {
    "id": "wal_1fIGZOrILmsCO0jw",
    "kind": "wallet",
    "name": "Treasury",
    "created_at": "2026-05-09T21:22:00.813Z"
  },
  "actor": {
    "kind": "user",
    "id": "usr_R4nW8kTq2LmZ6vXc",
    "name": "Dana Whitfield"
  },
  "mode": "live",
  "api_version": "v1",
  "created_at": "2026-05-09T21:22:00.813Z"
}
```

Snapshots, Deleted:

```json
{
  "id": "evt_Dz2PfJ6cY9hA4sRo",
  "kind": "event",
  "type": "wallet.deleted",
  "subject": {
    "kind": "wallet",
    "id": "wal_1fIGZOrILmsCO0jw"
  },
  "data": {
    "id": "wal_1fIGZOrILmsCO0jw",
    "kind": "wallet",
    "deleted": true
  },
  "actor": {
    "kind": "user",
    "id": "usr_R4nW8kTq2LmZ6vXc",
    "name": "Dana Whitfield"
  },
  "mode": "live",
  "api_version": "v1",
  "created_at": "2026-09-26T21:40:02.118Z"
}
```

- `link.deleted` and `webhook.deleted` carry the object as it was just before it went. `wallet.deleted`, and any event whose subject was already gone when it fired, carries only `id`, `kind` and `deleted: true`.
- A link's `payments_count` and `volume`, a customer's `stats`, and a referral's `payments_count`, `volume`, `fees` and `earned` are running totals. A delivery carries them as they were when it was sent; `GET /events` shows them as they are now.

## Event types

Every type below can be sent to a webhook. [`GET /event-types`](https://developer.402pay.co/api/events/types.md) returns the same list.

### Payments

| Type | When |
| --- | --- |
| `payment.created` | A payment was created and is waiting for the customer. For an API payment, when you create it. For a link, when checkout collects the customer's email, or when the transfer arrives if it never asked for one. |
| `payment.succeeded` | A payment was paid in full, or accepted, and its funds landed in the wallet or went to 402pay to pay the fees this business owes. That includes when the rest of an underpaid payment arrives. |
| `payment.underpaid` | A payment received less than the amount due. |
| `payment.overpaid` | A payment received more than the amount due and succeeded, or received a further transfer after it succeeded. Always right after `payment.succeeded`. |
| `payment.needs_review` | A payment needs a decision before it counts as paid. |
| `payment.failed` | A payment was canceled, by the business or by the customer at a link's checkout, or a declined card had no route left or wasn't retried within 30 minutes. No funds landed in the wallet. A decline with another card route left adds to the timeline instead. |
| `payment.expired` | A payment expired before any funds arrived. |

### Links

| Type | When |
| --- | --- |
| `link.created` | A payment link was created. |
| `link.updated` | A payment link's details changed. |
| `link.archived` | A payment link stopped taking payments. |
| `link.restored` | An archived payment link started taking payments again. |
| `link.deleted` | A payment link was deleted. |

### Customers

| Type | When |
| --- | --- |
| `customer.created` | A customer was added, by hand or when checkout collected their email. |
| `customer.updated` | A customer's details changed. |
| `customer.blocked` | A customer can no longer pay. |
| `customer.unblocked` | A blocked customer can pay again. |

### Wallet

| Type | When |
| --- | --- |
| `wallet.created` | The business wallet was created. |
| `wallet.deleted` | The business wallet was removed. |
| `wallet_transaction.created` | A send from the wallet started. Payments that land in the wallet send payment events instead. |
| `wallet_transaction.confirmed` | A send from the wallet was confirmed on chain. |
| `wallet_transaction.failed` | A send from the wallet failed on chain. When a send relayed from the dashboard fails on the network or is never seen there. A simulated business never sends it. |

### Business

| Type | When |
| --- | --- |
| `business.updated` | The business profile changed. A new `alert_email` counts once its code is [confirmed](https://developer.402pay.co/api/businesses/alert-email/confirm.md), not while it waits in `pending_alert_email`. |
| `business.activated` | The business finished setup and can take payments. |
| `checkout_settings.updated` | Checkout settings changed. |

### Billing

| Type | When |
| --- | --- |
| `fee.collected` | A payment's funds went to 402pay to pay the fees this business owes, instead of to the wallet. Right before `payment.succeeded` for a payment whose funds went to pay your fees. See [fees and billing](https://developer.402pay.co/guides/fees.md). |

### Developers

| Type | When |
| --- | --- |
| `api_key.created` | An API key was created. |
| `api_key.updated` | An API key's name or permissions changed. |
| `api_key.revoked` | An API key was revoked and stopped working. |
| `webhook.created` | A webhook endpoint was added. |
| `webhook.updated` | A webhook endpoint's settings or signing secret changed. |
| `webhook.deleted` | A webhook endpoint was removed. |

> An endpoint created with a restricted key can only subscribe to types whose subject that key can read. API key, business and referral events aren't covered by any permission, so only the dashboard or a key with full access can subscribe to them.

## Account events

These belong to a person, not to a business. They're recorded for the person's own security history, so webhooks never carry them and API keys can't list them.

| Type | When |
| --- | --- |
| `password.changed` | The password was changed in settings. |
| `password.reset` | The password was reset from an emailed link. |
| `email.changed` | The account's email address changed after the new one was confirmed. |
| `email.change_undone` | The email address was switched back from a link sent to the old one. |
| `passkey.added` | A passkey was added for signing in. |
| `passkey.removed` | A passkey was removed and can't sign in anymore. |
| `two_factor.enabled` | Sign-in now asks for an authenticator code. |
| `two_factor.disabled` | Sign-in no longer asks for an authenticator code. |
| `two_factor.recovery_code_used` | A recovery code stood in for the authenticator code. |
| `two_factor.recovery_codes_regenerated` | New recovery codes were made and the old ones stopped working. |
| `session.revoked` | A signed-in device was signed out from settings. |

## Sequences

What a payment sends, from creation to the end, in each case.

| Case | Events |
| --- | --- |
| Paid in full | `payment.created`, then `payment.succeeded` |
| Paid in full and collected as fees | `payment.created`, then `fee.collected`, then `payment.succeeded` |
| Paid too much | `payment.created`, then `payment.succeeded`, then `payment.overpaid` |
| Paid too little, then the rest arrived or you accepted | `payment.created`, then `payment.underpaid`, then `payment.succeeded` |
| Late or on another network, then accepted | `payment.created`, then `payment.needs_review`, then `payment.succeeded` |
| Canceled, or declined on every card route | `payment.created`, then `payment.failed` |
| Nothing arrived in time | `payment.created`, then `payment.expired` |

- A card declined with another attempt available isn't a webhook: the payment stays `pending` and its timeline gains a `card_attempt_failed` event.
- Each delivery is retried on its own schedule, so a retried `payment.created` can land after `payment.succeeded`. Go by the event's `created_at`, or fetch the payment, rather than the order deliveries arrive in.
- A new customer's `customer.created` arrives alongside their first payment's events, when checkout collects their email or you pass `customer_email`.

## Test events

[`POST /webhooks/{id}/test`](https://developer.402pay.co/api/webhooks/test.md) sends a signed sample of a type the endpoint receives, with `test: true`, a made-up subject such as `pmt_test_wcKbRA2NP8gXhlmr`, and only that ID in `data`. It checks your signature code and routing, not your handling of real data, so build against the shapes on this page.

See [webhooks](https://developer.402pay.co/guides/webhooks.md) for signatures, retries and resends.
