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.
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
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.
