checkout.paid and checkout.failed).POST to your URL with headers:{
"version": "v1",
"event_type": "checkout.paid",
"timestamp": "2025-11-17T12:34:56Z",
"idempotency_key": "550e8400-e29b-41d4-a716-446655440000",
"merchant_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"data": { }
}data holds event-specific fields (checkout id, amount, currency, status, customer email when present).checkout.paid payload.checkout.paid, checkout.approved, checkout.rejected, and optionally checkout.created.X-Webhook-Signature before trusting the body.sha256=<hex>.hmac.compare_digest in Python).request.get_data(as_text=True) in Flask, await request.body() in FastAPI before parsing JSON).401 if invalid.timestamp (replay protection).idempotency_key (store processed keys).2xx quickly; heavy work can run async.5xx only for temporary failures (KutanaPay retries). Use 4xx for permanent rejections.| Issue | Check |
|---|---|
| Signature fails | Raw body vs re-serialized JSON; wrong secret; header name case |
| No deliveries | HTTPS URL; firewall; URL reachable from internet |
| Disabled webhook | Platform delivery logs after repeated failures |