Webhooks
Last updated: 2026-05-22
Webhooks notify your system about payment order status updates.
Configuration
Webhook URL is configured for your merchant by platform administrators. Coordinate setup or changes through your platform support/operations process.
Delivery rules (actual)
- Method:
POST - Content-Type:
application/json - Timeout: up to 30 seconds per attempt
- Delivery is considered successful only on HTTP 200
- Any non-200 response is treated as failure and retried
- Retries use exponential backoff with jitter until the delay reaches about 1 hour, then continue on roughly hourly intervals with jitter (up to 100 attempts)
Headers
Content-Type: application/jsonIdempotency-Key: <delivery_id>X-Webhook-Signature: v=1, t=<unix_timestamp>, alg=hmac-sha256, s=<hex_signature>
Payload
Webhook body is a PaymentOrder object (same shape as GET /merchant/api/v1/payment/{id}).
Treat Idempotency-Key as an opaque delivery identifier. Most automated status updates use a payment/update-time based key; manual or replayed deliveries can use a different delivery ID.
The API may send repeated or corrective updates for the same payment. For example, PENDING -> PENDING can repeat, and a late provider confirmation can update FAILED to COMPLETED. Use Idempotency-Key to deduplicate deliveries safely and use the latest accepted paymentOrder.status for reconciliation.
Signature verification
Canonical message:
<timestamp>.<raw_request_body>
Secret format:
- Webhook secret is provided as a base64 string.
- Base64-decode it before computing HMAC.
Python example:
import base64
import hmac
import hashlib
import time
def verify_webhook(signature_header: str, raw_body: bytes, secret_b64: str, max_age_seconds: int = 600) -> bool:
parts = {}
for part in signature_header.split(","):
k, v = part.strip().split("=", 1)
parts[k.strip()] = v.strip()
if parts.get("v") != "1":
return False
if parts.get("alg") != "hmac-sha256":
return False
try:
ts = int(parts["t"])
except Exception:
return False
if max_age_seconds is not None and abs(int(time.time()) - ts) > max_age_seconds:
return False
secret = base64.b64decode(secret_b64)
message = str(ts).encode("utf-8") + b"." + raw_body
expected = hmac.new(secret, message, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("s", ""))
Processing checklist
- Verify
X-Webhook-Signatureagainst raw request body. - Deduplicate by
Idempotency-Key. - Process asynchronously in your queue/worker.
- Return HTTP 200 quickly after enqueue.