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 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.
{ "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"}| 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} returns it, timeline included. |
link | The link, with its payment count and volume. |
customer | The customer, with their stats. |
wallet | Only id, kind, name and created_at. Balances and keys are never included. |
wallet_transaction | The wallet transaction. |
api_key | The key with its name, mode and permissions. Its secret is redacted, never sent. |
webhook | The endpoint with its secret_hint. The signing secret is never sent. |
business | The business 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 of type collection, with the balance you owed right after it. |
{ "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"}link.deletedandwebhook.deletedcarry the object as it was just before it went.wallet.deleted, and any event whose subject was already gone when it fired, carries onlyid,kindanddeleted: true.- A link's
payments_countandvolume, a customer'sstats, and a referral'spayments_count,volume,feesandearnedare running totals. A delivery carries them as they were when it was sent;GET /eventsshows them as they are now.
Event types
Every type below can be sent to a webhook. GET /event-types 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, 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. |
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. |
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
pendingand its timeline gains acard_attempt_failedevent. - Each delivery is retried on its own schedule, so a retried
payment.createdcan land afterpayment.succeeded. Go by the event'screated_at, or fetch the payment, rather than the order deliveries arrive in. - A new customer's
customer.createdarrives alongside their first payment's events, when checkout collects their email or you passcustomer_email.
Test events
POST /webhooks/{id}/test 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 for signatures, retries and resends.