Skip to main content

Webhooks Guide

Webhooks notify your backend when payment and payout states change. Your handler should verify the signature, process the event idempotently, and return a 2xx response quickly.
Prerequisites: You need a public callback URL and its dedicated webhook signing secret. Create/rotation reveals this secret once; store it separately from the API credential.

Events

Withdrawals are delivered through the same payment.* event names as deposits. Use data.result.payment.destination: "out" to identify withdrawal callbacks. payment.processing may appear in status responses, but current merchant webhook delivery does not emit it as a callback event.

Payload

Webhook deliveries use an outer envelope. The payment object is under data.result.payment.
Internal fields prefixed with _ are not included in the delivered webhook body. Do not build integrations around _payment_id or other internal-only fields.
identifiers.utr is optional and appears only when a non-empty UTR/reference value is available.

Headers

Verify Signatures

Use the exact raw request body bytes. Re-serializing parsed JSON changes whitespace/key ordering and will break verification. Prefer X-Webhook-Signature-V3 when present. It binds the timestamp, nonce, delivery id, and raw body. X-Webhook-Signature-V2 and X-Data-Hash remain for backward-compatible verification. Every active delivery uses a dedicated webhook-only secret. Historical API-compatible webhook rows are blocked until you explicitly rotate the webhook secret and update the receiver. Rotating an API credential does not rotate a webhook secret. When payment.in or payment.out creates a callback from params.payment.webhook_url, derive its webhook secret from the exact test or production API secret used to authenticate that create as HMAC-SHA256(apiSecret, "quadpay:merchant-webhook-signing:v1"), encoded as lowercase hex. The destination is bound to that payment and does not subscribe to other merchant payments. Do not use the API secret itself for verification.

Retry Behavior

A webhook delivery succeeds on any HTTP 2xx response. Non-2xx responses, timeouts, network errors, URL validation failures, or circuit-breaker blocks are failures. Defaults: Retry delay uses exponential backoff with jitter and a 24-hour cap. Delivery order is not guaranteed, so your handler must be idempotent.

Handling Statuses

Use payment.status.final to decide whether the payment reached a terminal state.

Per-Payment Webhook URLs

You can pass webhook_url in payment.in and payment.out requests:
In production, use a public HTTPS URL. The gateway validates the URL when the payment is created and again before delivery. Each URL entry is bound to that payment, even when multiple payments use the same URL.

Best Practices

  • Verify every signature before processing.
  • Return 2xx quickly and process heavy work asynchronously.
  • Use id plus payment.status.status as a deduplication key.
  • Store request_id, identifiers.c_id, and identifiers.h_id for support and reconciliation.
  • Treat webhook delivery as at-least-once.