> For the complete index of the 402pay docs, see [llms.txt](https://402pay.co/docs/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://402pay.co/docs/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": "49.00",
    "currency": "USD",
    "fee_payer": "business",
    "customer_fee": "0.00",
    "amount_received": "49.00",
    "reporting": {
      "currency": "USD",
      "amount": "49.00",
      "fee": "0.00",
      "transaction_fee": "0.25",
      "customer_fee": "0.00",
      "net": "48.75"
    },
    "fee_rate_bps": 0,
    "method": {
      "rail": "crypto",
      "asset": "USDC",
      "network": "ethereum",
      "amount": "49.00",
      "expected_amount": "49.00",
      "overpaid_amount": null,
      "from_address": "0x136e44a2738dea5217e8d6745463d2a9a84d1421",
      "tx_hash": "0xc16a611d90302ba917593ed9743f5e734a556e8d2f35bb0d60a2ca02ac99bb20",
      "explorer_url": "https://etherscan.io/tx/0xc16a611d90302ba917593ed9743f5e734a556e8d2f35bb0d60a2ca02ac99bb20"
    },
    "settlement": {
      "destination": "wallet",
      "asset": "USDC",
      "network": "ethereum",
      "amount": "49.00",
      "wallet_id": "wal_1fIGZOrILmsCO0jw",
      "address": "0x9a3c416D4A81669C01f4515bBDEB93C45d3813fe",
      "tx_hash": "0xc16a611d90302ba917593ed9743f5e734a556e8d2f35bb0d60a2ca02ac99bb20",
      "explorer_url": "https://etherscan.io/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:10.247Z",
    "created_at": "2026-09-26T21:22:47.038Z",
    "updated_at": "2026-09-26T21:23:10.247Z",
    "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": "ethereum"
        }
      },
      {
        "type": "detected",
        "created_at": "2026-09-26T21:23:07.047Z",
        "data": {
          "amount": "49.00",
          "network": "ethereum",
          "tx_hash": "0xc16a611d90302ba917593ed9743f5e734a556e8d2f35bb0d60a2ca02ac99bb20"
        }
      },
      {
        "type": "confirmation",
        "created_at": "2026-09-26T21:23:08.647Z",
        "data": {
          "current": 1,
          "required": 2
        }
      },
      {
        "type": "confirmation",
        "created_at": "2026-09-26T21:23:10.247Z",
        "data": {
          "current": 2,
          "required": 2
        }
      },
      {
        "type": "succeeded",
        "created_at": "2026-09-26T21:23:10.247Z",
        "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` | Always `live`. |
| `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://402pay.co/docs/api/payments/retrieve.md) returns it, timeline included. |
| `link` | The [link](https://402pay.co/docs/api/links/retrieve.md), with its payment count and volume. |
| `customer` | The [customer](https://402pay.co/docs/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://402pay.co/docs/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://402pay.co/docs/api/webhooks/list.md) with its `secret_hint`. The signing secret is never sent. |
| `business` | The [business](https://402pay.co/docs/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. |

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": "199.00",
    "currency": "USD",
    "success_url": null,
    "status": "active",
    "disabled_at": null,
    "payments_count": 0,
    "volume": {
      "amount": "0.00",
      "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": "228.10",
        "currency": "USD"
      },
      "average": {
        "amount": "114.05",
        "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://402pay.co/docs/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. 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 24 hours. 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. |

### Business

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

### 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 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://402pay.co/docs/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://402pay.co/docs/guides/webhooks.md) for signatures, retries and resends.
