Webhooks Overview
CROSS Pay sends an HTTP POST request to the merchant's registered
callback_url every time a payment status changes. This page covers the
flow, event types, and payload structure that you need to know when
first integrating webhooks as a merchant.
Signature verification, retry, and the checklist are split into separate pages.
| Next | Where to read |
|---|---|
| Signature verification code, replay protection | Verifying Webhooks |
| Retry, idempotency, dedup | Delivery & Retry |
| Pre-production review | Production Checklist |
| Exact field dictionary per event | Webhook Events Reference |
The CROSS Pay API captures events and a separate delivery service handles HTTP transmission, signing, and retries. From the merchant's perspective this guide presents both as a single unified behavior.
Flow
Event catalog
Event (type) | Trigger | Merchant follow-up action |
|---|---|---|
payment.completed | PG / chain reports confirmation → PROCESSING → PAID | Confirm order, ship, deliver digital asset |
payment.failed | PG / chain reports failure → PROCESSING → FAILED | Fail the order, notify the user |
payment.expired | TTL expiry worker cleans up unpaid sessions → EXPIRED (from both PENDING and PROCESSING) | Restore cart, prompt re-payment |
payment.cancelled | Merchant explicitly cancels from PENDING → CANCELLED | Mark order cancelled |
For the exact payload schema and field dictionary of each event, see the Webhook Events Reference.
PROCESSING is the external PG handoff windowpayment.completed / payment.failed always terminate through
PROCESSING. A payment enters PROCESSING the moment
POST /checkout/{id}/quotes (SelectQuote) creates the PG session. While in
this window the merchant cannot call cancel alone (409 INVALID_TRANSITION).
You must wait for the PG webhook result or for TTL expiry.
payment.refundedThe refund completion event (payment.refunded) is not currently
delivered. Query refund results via GET /v1/payments/:id.
Payload structure
CROSS Pay builds payloads using the Standard Webhooks v1 envelope.
{
"type": "payment.completed",
"timestamp": "2026-04-21T12:00:00Z",
"data": {
"payment": {
"id": "pay_abc123",
"status": "PAID",
"currency": "KRW",
"amount": "50000",
"payer_uid": "user_789",
"receipt_id": "rcpt_xyz",
"provider": "binancepay",
"paid_at": "2026-04-21T12:00:00Z",
"metadata": { "order_id": "shop-order-789" }
}
}
}
| Field | Meaning |
|---|---|
type | Dot-delimited event name (e.g. payment.completed) |
timestamp | Time the domain event occurred (paid_at / failed_at, etc.). Not the delivery time. |
data | Event-specific payload. Every event contains a data.payment object. |
All time fields are ISO-8601 UTC (Z suffix).
Parse on the merchant side as UTC, then convert to the display timezone.
HTTP request format
| Field | Value |
|---|---|
| Method | POST |
| Content-Type | application/json |
| Body | Raw bytes of the envelope JSON above |
The request also carries the following four headers.
| Header | Format | Meaning |
|---|---|---|
webhook-id | UUID | Per-event identifier. Use as the idempotency dedup key. |
webhook-timestamp | Unix epoch seconds (string) | Delivery time. Used for replay protection. |
webhook-signature | v1,<base64> (multiple pairs separated by space) | HMAC-SHA256 signature. See Verifying Webhooks for details. |
Content-Type | application/json | Body encoding |
POST /your/webhook/path HTTP/1.1
Host: shop.example.com
Content-Type: application/json
webhook-id: 0190a1b2-c3d4-7e5f-89ab-cdef01234567
webhook-timestamp: 1745236800
webhook-signature: v1,K8fXq2y...base64...
{"type":"payment.completed","timestamp":"2026-04-21T12:00:00Z","data":{...}}
metadata passthrough
The metadata JSON sent with POST /v1/payments is passed through
unchanged into the data.payment.metadata field of every webhook payload.
// Payment creation request
{
"currency": "KRW",
"amount": 50000,
"metadata": {
"order_id": "shop-order-789",
"user_id": "u_42"
}
}
// data.payment.metadata on every subsequent webhook
{
"order_id": "shop-order-789",
"user_id": "u_42"
}
Merchants can put their own order ID or user ID in metadata, so that on
webhook receipt they can map to their internal system without an extra API call.
CROSS Pay cannot search payments by keys inside metadata.
Keep a metadata.order_id ↔ payment.id mapping table on the merchant side.
Payment state machine
Key rules:
PENDING → PAID/FAILEDdirect transitions are not allowed. Every PG / chain termination goes throughPROCESSING.PROCESSING → CANCELLEDis blocked (PG race condition). To abandon aPROCESSINGpayment, rely on expiry (EXPIRED) or a PGFAILEDtermination.PROCESSING → PROCESSINGre-entry is allowed (payment method change). When a secondSelectQuotecomes in, the existing PG session is closed and a new quote/session is issued.- State transitions are one-way; the same payment never fires two terminal events.
If your SDK's status switch has no default/unknown branch, add an explicit
case for PROCESSING. We recommend showing an intermediate state like
"Processing at PG — waiting for result" in the UI. Do not run side-effects
like order confirmation or shipping until the webhook delivers a terminal event.
callback URL registration
The callback_url is configured per merchant. CROSS Pay does not currently
provide a self-serve endpoint for merchants to change it.
- New registration / change: email support@crosspay.ai
- Requirements: HTTPS recommended, publicly routable, able to receive POST bodies
If you need to expose a local merchant endpoint to the internet, the following tools help.
webhook.site— instant receiver to inspect payloadsngrok— tunnel a local server to a public URLsmee.io— public URL → local proxy
Secret (Callback Secret)
The merchant secret used for signature verification is stored encrypted at rest on the CROSS Pay side. The plaintext secret is delivered to the merchant out-of-band (email, partner channel) only once, at issuance time.
- Storage: store the secret in a secret manager (e.g. AWS Secrets Manager, GCP Secret Manager) or environment variables. Do not commit it to code or source control.
- Reissue: if lost, email support@crosspay.ai.
See Verifying Webhooks for signature verification using the secret, with code examples.
Things to know when integrating
-
payment.createdis not delivered. The result of payment creation is returned synchronously in thePOST /v1/paymentsresponse. -
The
payment.completedpayload does not include a fee breakdown. If you need settlement data, enrich the data viaGET /v1/payments/:id. -
For on-chain payments,
paid_atis the finality confirmation time. It is not the broadcast time of the TX, but the moment CROSS Pay observed on-chain finality. -
failure_reasonis a controlled vocabulary. The current enum values are below. We recommend that merchants include a default branch that falls back toUNKNOWNfor any unknown value.Value Meaning UNKNOWNFailure not classified by the PG. Fallback. DECLINEDThe PG declined the payment. TX_REVERTEDThe on-chain TX was mined, but the contract call reverted. AMOUNT_MISMATCHThe on-chain event amount does not match the expected value. TOKEN_MISMATCHThe on-chain event token does not match the expected value. ONCHAIN_TIMEOUTThe on-chain confirmation was not observed before expiry. The original PG message is not delivered to the merchant (preserved only in server logs).
-
The
providervalue identifies the PG that processed the payment. Current possible values:binancepay,payletter,worldpay,onchain(andcrosspayfor on-chain CROSS Pay payments). Do not let merchant code depend on theprovidervalue — build your business logic aroundstatusand amount. Note that theprovidercolumn in the payment_method response (e.g.binance_pay, snake-case) and thepayment.providerin webhook payloads (e.g.binancepay, no separator) may use different naming. -
Stale webhooks are ignored after a PG switch. When the buyer changes payment method on the same checkout,
payment.provideris updated. If the old PG sends a late callback, CROSS Pay ACKs with 200 and discards it without dispatching. As a result, the same payment never delivers two terminal events to the merchant.
Next steps
- Verifying Webhooks — HMAC signature verification and replay protection
- Delivery & Retry — retry policy, idempotency, dedup
- Production Checklist — pre-production review items
- Webhook Events Reference — exact field dictionary per event