Webhook Best Practices
Intro
Eight concrete habits separate a webhook handler that works from one that silently drops events under load: acknowledge fast, deduplicate byevent_id, verify before trusting, and don’t treat webhooks as the only source of truth.
Context
These practices assume you’ve already read Webhooks Overview for the envelope/delivery model, Signature Verification for the HMAC check, and Events for payload shapes. This page is what to do with all three once requests are hitting your endpoint in production.Hows
1. Respond fast
Return200 OK within 5 seconds. Process the event asynchronously after acknowledging:
2. Handle duplicates
Use theevent_id field (also sent as the X-Khaime-Event-Id header) for idempotency. Delivery is at-least-once — the same event can arrive more than once, and retries reuse the same event_id:
3. Verify before processing
Always verify the signature using the raw request body before acting on a webhook. Reject unverified requests with401.
4. Handle missing events
If a webhook is missed and you used the Sessions API, you can check the payment status directly:200 promptly — after 3 delivery attempts, Khaime stops retrying and the event is marked failed.
5. Use the envelope fields
Every webhook includes useful envelope fields beyond the event data:api_version— confirms which payload schema version was used (see API Versioning)is_live— distinguish sandbox events from productionbusiness_id— identify which merchant the event belongs to (useful in marketplace setups)
6. Log everything
Log raw webhook payloads for debugging. Include theevent_id, event_type, and processing result.
7. Use HTTPS
Webhook URLs must use HTTPS in production. Khaime will not deliver webhooks to HTTP endpoints in live mode.8. Plan for new event types
New event types may be added without a version change. Your webhook handler should gracefully ignore unknownevent_type values rather than erroring:
Whys
Most of these practices trace back to one property of the delivery model: at-least-once, with a short timeout and a small, fixed number of retries. Acknowledging within 5 seconds and processing asynchronously (#1) exists because your handler competes with a 10-second timeout — anything that risks running long enough to miss it turns one delivery into a retried duplicate. Deduplicating byevent_id (#2) exists because “at-least-once” is a guarantee about delivery, not about your side effects; without a dedupe check, a retried event double-processes. Defaulting unknown event types to a no-op (#8) exists because the schema is additive by design (see API Versioning) — new event types ship without warning, and a handler that throws on unrecognized input turns a harmless addition into an outage.
Why nots
Webhooks are not a queueing system — don’t build logic that assumes events for a single object arrive in a guaranteed order, and don’t chain multi-step business logic directly inside the request handler (that’s what #1 asymmetry is for: acknowledge synchronously, do the work off the request path). Don’t skip signature verification for convenience, don’t dedupe with an in-memorySet (it resets on every deploy/restart and doesn’t work across multiple server instances), and don’t treat a 200 response from your endpoint as proof the event was fully processed — it only proves it was received. If your business logic can fail after acknowledgment, you need your own retry/dead-letter handling downstream of the webhook, since Khaime’s retries stop once you return 200.