Skip to main content

Webhook Events reference

This page is a payload schema dictionary for the webhook events that CROSS PAY API delivers. For emission points, integration flow, and merchant-side handling patterns, see Webhooks Overview.

Scope

This page focuses on the JSON body schema of each envelope.

HTTP request headers

HeaderFormatDescription
Content-Typeapplication/jsonBody encoding
webhook-idUUIDOutbox event identifier. Use as the idempotency dedup key
webhook-timestampUnix epoch seconds (string)Send time. For replay protection (recommended ±300s)
webhook-signaturev1,<base64> (space-separated for multiple)HMAC-SHA256 signature over ${id}.${ts}.${body}

For the signature verification algorithm and code examples, see Verifying Webhooks.

Common envelope

CROSS PAY uses the Standard Webhooks v1 envelope specification.

{
"type": "<dot-delimited event name>",
"timestamp": "<ISO-8601 UTC, time the domain event occurred>",
"data": {
"payment": { ... },
"refund": { ... }
}
}
FieldTypeDescription
typestringDot-delimited event name. e.g., payment.completed
timestampISO-8601When the domain event occurred (paid_at / failed_at / ...). UTC Z
dataobjectPer-event payload. The payment object is always present

data.payment common fields

The data.payment object across all events is drawn from the following field set. Whether a field is present depends on the event type.

FieldTypeAlways?Description
idstringPayment identifier (pay_...)
statusstringPayment status (fixed per event — see table below)
currencystringISO-4217 currency code. e.g., KRW, USD
amountstringPayment amount. Serialized as a string to avoid precision loss
payer_uidstringPayer identifier
metadataobjectMetadata supplied by the merchant at payment creation. {} if empty
receipt_idstringPG-issued receipt ID (payment.completed only)
providerstringPG that processed the payment (payment.completed only). e.g., binancepay
failure_reasonstringFailure reason enum (payment.failed only). See enum table below
paid_atISO-8601Payment confirmation time (payment.completed)
cancelled_atISO-8601Cancellation time (payment.cancelled)
failed_atISO-8601Failure time (payment.failed)
expired_atISO-8601Expiry time (payment.expired)

status value per event

EventPrevious statestatus value
payment.completedPROCESSINGPAID
payment.failedPROCESSINGFAILED
payment.expiredPENDING or PROCESSINGEXPIRED
payment.cancelledPENDINGCANCELLED
PROCESSING transition rule

payment.completed and payment.failed always terminate via PROCESSING (starting when POST /checkout/{id}/quotes creates a PG session). Until the PG webhook arrives, the merchant cannot call cancel on its own — the request is rejected with 409 INVALID_TRANSITION. For the full lifecycle, see Webhooks Overview.

failure_reason enum

ValueMeaning
UNKNOWNFailure not classified by the PG. Fallback
DECLINEDThe PG declined the payment
TX_REVERTEDOn-chain TX was mined but the contract call reverted
AMOUNT_MISMATCHOn-chain event amount does not match the expected value
TOKEN_MISMATCHOn-chain event token does not match the expected value
ONCHAIN_TIMEOUTThe on-chain confirmation was not observed before expiry

This set is open to extension. Merchants should fall back to UNKNOWN for values they do not recognize.

Payment state machine

  • PENDING → PAID/FAILED is not allowed directly. Before the PG/chain reports a result, the payment must first transition through PROCESSING.
  • PROCESSING → CANCELLED is blocked. To avoid races with the PG, the merchant cancel API rejects this transition with 409 INVALID_TRANSITION.
  • Terminal states (PAID / FAILED / EXPIRED / CANCELLED / *_REFUNDED) do not transition to other terminal states. The same payment never emits two different terminal events.

payment.completed

When the PG or chain reports confirmation, payment transitions to PAID and this event is emitted.

Fields

FieldRequiredNotes
id
statusAlways PAID
currency
amount
payer_uid
receipt_idPG-issued receipt ID
providerPG identifier (binancepay, payletter, ...)
paid_at
metadata

Sample

{
"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" }
}
}
}
fee breakdown

Settlement-level fee information is not included in this payload. If you need it, enrich via GET /v1/payments/:id.


payment.failed

When the PG or chain reports failure, payment transitions to FAILED and this event is emitted.

Fields

FieldRequiredNotes
id
statusAlways FAILED
currency
amount
payer_uid
failure_reasonSee failure_reason enum above
failed_at
metadata

Sample

{
"type": "payment.failed",
"timestamp": "2026-04-21T12:05:00Z",
"data": {
"payment": {
"id": "pay_abc123",
"status": "FAILED",
"currency": "KRW",
"amount": "50000",
"payer_uid": "user_789",
"failure_reason": "DECLINED",
"failed_at": "2026-04-21T12:05:00Z",
"metadata": { "order_id": "shop-order-789" }
}
}
}

payment.expired

When the session TTL elapses, the expiry worker transitions in-flight payments (PENDING or PROCESSING) to EXPIRED and emits this event.

Fields

FieldRequiredNotes
id
statusAlways EXPIRED
currency
amount
payer_uid
expired_at
metadata

Sample

{
"type": "payment.expired",
"timestamp": "2026-04-21T12:30:00Z",
"data": {
"payment": {
"id": "pay_abc123",
"status": "EXPIRED",
"currency": "KRW",
"amount": "50000",
"payer_uid": "user_789",
"expired_at": "2026-04-21T12:30:00Z",
"metadata": { "order_id": "shop-order-789" }
}
}
}

payment.cancelled

When the merchant explicitly cancels a PENDING payment via POST /v1/payments/{id}/cancel, this event is emitted. Cancel calls on a PROCESSING payment are rejected with 409 INVALID_TRANSITION, so this event does not fire for that flow — it terminates as payment.expired or payment.failed instead.

Fields

FieldRequiredNotes
id
statusAlways CANCELLED
currency
amount
payer_uid
cancelled_at
metadata

Sample

{
"type": "payment.cancelled",
"timestamp": "2026-04-21T12:10:00Z",
"data": {
"payment": {
"id": "pay_abc123",
"status": "CANCELLED",
"currency": "KRW",
"amount": "50000",
"payer_uid": "user_789",
"cancelled_at": "2026-04-21T12:10:00Z",
"metadata": { "order_id": "shop-order-789" }
}
}
}

payment.refunded (Preview)

The refund-completed event envelope (including the data.refund object) is defined in code, but no emission path is currently wired up in the CROSS PAY API. This event does not reach the merchant endpoint. To check refund results, query GET /v1/payments/:id.

Once the emission path is wired up, this page will be updated with the official schema and samples.


Known constraints

  • payment.created is not emitted. Payment creation results are delivered synchronously in the POST /v1/payments response.
  • Empty metadata is serialized as {}. It is never null.
  • All time fields are ISO-8601 UTC (Z suffix).
  • amount is a string. Serialized as a string rather than a number to avoid precision loss. Parse with a decimal library on the merchant side.