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 underdata.result.payment.
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. PreferX-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.
Merchant-wide callbacks use the active production API secret when available.
Rotating that API credential therefore also changes callback verification after
the short gateway cache window. If the merchant has no production API key, or
you explicitly rotate the webhook secret, the webhook uses a dedicated secret
shown once by that operation.
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
Usepayment.status.final to decide whether the payment reached a terminal state.
Per-Payment Webhook URLs
You can passwebhook_url in payment.in and payment.out requests:
Best Practices
- Verify every signature before processing.
- Return 2xx quickly and process heavy work asynchronously.
- Use
idpluspayment.status.statusas a deduplication key. - Store
request_id,identifiers.c_id, andidentifiers.h_idfor support and reconciliation. - Treat webhook delivery as at-least-once.
