Skip to main content

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.

NextWhere to read
Signature verification code, replay protectionVerifying Webhooks
Retry, idempotency, dedupDelivery & Retry
Pre-production reviewProduction Checklist
Exact field dictionary per eventWebhook Events Reference
System composition

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)TriggerMerchant follow-up action
payment.completedPG / chain reports confirmation → PROCESSING → PAIDConfirm order, ship, deliver digital asset
payment.failedPG / chain reports failure → PROCESSING → FAILEDFail the order, notify the user
payment.expiredTTL expiry worker cleans up unpaid sessions → EXPIRED (from both PENDING and PROCESSING)Restore cart, prompt re-payment
payment.cancelledMerchant explicitly cancels from PENDINGCANCELLEDMark order cancelled

For the exact payload schema and field dictionary of each event, see the Webhook Events Reference.

PROCESSING is the external PG handoff window

payment.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.refunded

The 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" }
}
}
}
FieldMeaning
typeDot-delimited event name (e.g. payment.completed)
timestampTime the domain event occurred (paid_at / failed_at, etc.). Not the delivery time.
dataEvent-specific payload. Every event contains a data.payment object.
Time format

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

FieldValue
MethodPOST
Content-Typeapplication/json
BodyRaw bytes of the envelope JSON above

The request also carries the following four headers.

HeaderFormatMeaning
webhook-idUUIDPer-event identifier. Use as the idempotency dedup key.
webhook-timestampUnix epoch seconds (string)Delivery time. Used for replay protection.
webhook-signaturev1,<base64> (multiple pairs separated by space)HMAC-SHA256 signature. See Verifying Webhooks for details.
Content-Typeapplication/jsonBody 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.

metadata is not indexed

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/FAILED direct transitions are not allowed. Every PG / chain termination goes through PROCESSING.
  • PROCESSING → CANCELLED is blocked (PG race condition). To abandon a PROCESSING payment, rely on expiry (EXPIRED) or a PG FAILED termination.
  • PROCESSING → PROCESSING re-entry is allowed (payment method change). When a second SelectQuote comes 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.
Merchant-side branching

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
Tools for exposing a local endpoint

If you need to expose a local merchant endpoint to the internet, the following tools help.

  • webhook.site — instant receiver to inspect payloads
  • ngrok — tunnel a local server to a public URL
  • smee.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.created is not delivered. The result of payment creation is returned synchronously in the POST /v1/payments response.

  • The payment.completed payload does not include a fee breakdown. If you need settlement data, enrich the data via GET /v1/payments/:id.

  • For on-chain payments, paid_at is the finality confirmation time. It is not the broadcast time of the TX, but the moment CROSS Pay observed on-chain finality.

  • failure_reason is a controlled vocabulary. The current enum values are below. We recommend that merchants include a default branch that falls back to UNKNOWN for any unknown value.

    ValueMeaning
    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 provider value identifies the PG that processed the payment. Current possible values: binancepay, payletter, worldpay, onchain (and crosspay for on-chain CROSS Pay payments). Do not let merchant code depend on the provider value — build your business logic around status and amount. Note that the provider column in the payment_method response (e.g. binance_pay, snake-case) and the payment.provider in 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.provider is 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