Delivery

Webhooks

Receive signed payment events, return 2xx only after durable acceptance, and use delivery ids for idempotency.

Delivery model

ZamaPay emits project-level webhook events after the payment read model and finality gate agree. Each delivery records attempt count, HTTP status, response body, error, retry state, and dead-letter state.

Webhook payloads are at-least-once. The merchant backend must use `x-zamapay-webhook-id` or the event id as an idempotency key.

HeaderExampleUse
x-zamapay-webhook-iddeliv_...Delivery id for idempotent processing.
x-zamapay-event-idevt_...Immutable event id.
x-zamapay-webhook-timestamp2026-05-07T05:00:00ZSigned timestamp.
x-zamapay-webhook-signaturev1=0x...Payload authenticity proof.
x-zamapay-webhook-algorithmkeccak256.secret_prefix.v1Signature algorithm version.
Outbox diagnostics
Immutable event, delivery attempt, result, and manual resend stay separate.
Eventpayment.finality_safe
Deliveryattempt 1 / delivered
Signaturekeccak256.secret_prefix.v1
Retrymanual resend available

Verify a webhook

Verification canonicalizes the JSON body, builds `deliveryId.timestamp.canonicalBody`, and checks the keyed digest with the webhook secret. Reject missing headers before reading business fields.

import { keccak256, toUtf8Bytes } from "ethers"

export function verifyZamaPayWebhook(headers, body, secret) {
  const deliveryId = headers["x-zamapay-webhook-id"]
  const timestamp = headers["x-zamapay-webhook-timestamp"]
  const signature = headers["x-zamapay-webhook-signature"]
  const algorithm = headers["x-zamapay-webhook-algorithm"]

  if (algorithm !== "keccak256.secret_prefix.v1") throw new Error("unsupported algorithm")

  const canonicalBody = JSON.stringify(body)
  const base = `${deliveryId}.${timestamp}.${canonicalBody}`
  const expected = "v1=" + keccak256(toUtf8Bytes(`${secret}.${base}`))
  if (signature !== expected) throw new Error("invalid signature")

  return body
}

Ready to wire a merchant project?

Create the project in the console, then keep external checkout creation on the project API-key path.

Open console