Notification webhooks

Payload envelope

The canonical JSON envelope and its identity invariants.

Every notification webhook carries a single JSON object with a fixed top-level shape. The envelope is identical across all notification classes; only the value of notification_class and the two conditional fields (finality_outcome, hold_reason) vary.

Canonical shape

{
  "delivery_record_id": "string",
  "payment_intent_id": "string",
  "merchant_id": "string",
  "notification_class": "payment_observed | payment_finalized | payment_held | duplicate_payment_incident",
  "attempt_id": "string | null",
  "chain_id": "string | null",
  "finality_outcome": "paid | refunded | failed | null",
  "hold_reason": "sanctions | kyt_timeout | null"
}

Field rules

FieldTypeRules
delivery_record_idstringIdentifies the semantic notification record being delivered. This is the deduplication anchor: every retry and every manual re-delivery of the same notification carries the same delivery_record_id.
payment_intent_idstringThe canonical merchant-facing correlation key. Always present. Correlate the notification to a payment in your system by this value.
merchant_idstringThe merchant the notification is addressed to. Always present.
notification_classenumOne of payment_observed, payment_finalized, payment_held, duplicate_payment_incident. Always present. Determines how the conditional fields below are populated.
attempt_idstring | nullAn upstream payment-attempt reference for traceability only. MUST NOT be interpreted as the identity of this notification record. null when not available.
chain_idstring | nullNumeric chain identifier encoded as a string, when available; otherwise null.
finality_outcomeenum | nullPresent (non-null) only when notification_class is payment_finalized. One of paid, refunded, failed. null for every other class.
hold_reasonenum | nullPresent (non-null) only when notification_class is payment_held. One of sanctions, kyt_timeout. null for every other class.

finality_outcome values in plain terms

ValueWhat it means
paidPayment confirmed on-chain and reconciled — the deposit was swept to the merchant's approved destination, and the protocol fee was collected on-chain.
refundedThe merchant returned a reject decision with a refund destination; the deposit was returned to the customer, and the protocol fee was collected on-chain.
failedThe payment reached a terminal failure state without settlement or merchant-directed refund. No funds moved.

A fourth value, duplicate_payment, is not carried on a payment_finalized notification — a duplicate is surfaced as its own duplicate_payment_incident notification, and on the API status projection. See Statuses and projections.

Conditional-field invariants

  • finality_outcome MUST be non-null if and only if notification_class is payment_finalized.
  • hold_reason MUST be non-null if and only if notification_class is payment_held.
  • For payment_observed and duplicate_payment_incident, both finality_outcome and hold_reason MUST be null.

The full per-class field matrix is tabulated in the Event catalog.

Notification identity

A notification record is identified by the triple (endpoint, payment_intent_id, notification_class). Exactly one record exists per registered endpoint, per payment intent, per class. Consequences:

  • A single payment_intent_id MAY produce several records — for example a payment_held record and a later payment_finalized record, or a payment_finalized record alongside a duplicate_payment_incident record. Each is a distinct envelope with its own delivery_record_id.
  • All deliveries of one record — first send, automatic retries, manual re-delivery — share one delivery_record_id. This is the value to deduplicate on.

The notification is not the truth

A webhook is a side effect of a payment event, not the authoritative record of it. The envelope reflects state at the moment the notification was constructed. Reconcile against canonical state through the API and the signed Proof-of-Payment artifact — never treat envelope receipt, or its absence, as settlement truth. See Delivery semantics.

Signature

The raw bytes of this JSON body are the message signed by the notification HMAC. Verify the signature against the exact bytes received, before parsing — see Signature verification.

On this page