Skip to main content

Webhook Best Practices

Intro

Eight concrete habits separate a webhook handler that works from one that silently drops events under load: acknowledge fast, deduplicate by event_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

Return 200 OK within 5 seconds. Process the event asynchronously after acknowledging:

2. Handle duplicates

Use the event_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 with 401.

4. Handle missing events

If a webhook is missed and you used the Sessions API, you can check the payment status directly:
For payments created via the Charge API, webhook delivery is the only confirmation mechanism. Ensure your webhook endpoint is reliable and returns 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 production
  • business_id — identify which merchant the event belongs to (useful in marketplace setups)

6. Log everything

Log raw webhook payloads for debugging. Include the event_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 unknown event_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 by event_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-memory Set (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.