Skip to main content
Outbound webhooks push events to a URL you control, so you don’t have to poll the REST API for new activity. Register an endpoint once and iZap posts each subscribed event to it as it happens — the direction of Analytics MCP and the REST API is you calling iZap; this is iZap calling you.
Looking for the inbound side — Meta’s Cloud API webhook that delivers WhatsApp messages to iZap? That’s internal to iZap’s own WhatsApp connection. What’s on this page is iZap’s own outbound delivery to your endpoint.

Register an endpoint

url must be https:// on the standard port (443) — a custom port (e.g. https://example.com:8443/hook) is rejected, as is a private or loopback address, with 422. subscribed_events can be left as [] to receive every event in the catalog, including ones added later. The response is the only place the signing secret is ever shown in full:
Store signing_secret right away — there is no endpoint to read it back later. If you lose it, rotate it for a new one.
Registering an endpoint requires a plan with event webhooks included. Without one, the call returns 403 with code: "plan_limit_event_webhooks".

Event catalog

The catalog is versioned by addition only — existing event payloads don’t change shape once shipped, so parsing today’s message.received stays safe as new event types are added.

Payload

The body is serialized with sorted keys and no extra whitespace, and that exact byte sequence is what gets signed — verify against the raw request body, not a re-serialized parse of it.

Headers

A retried or manually resent delivery carries the same izap-webhook-id and payload bytes, but a fresh izap-webhook-timestamp and both signatures recomputed at send time. Once you’ve verified izap-webhook-signature-v2, izap-webhook-id is safe to use as your idempotency key — v2 signs the id itself, so a delivery replayed under a different id fails verification. Dedupe on the id only after that check passes, never on the timestamp or the signature alone: both change on every retry and resend of the same event.

Verify the signature

izap-webhook-signature-v2 is the recommended check — it’s the only one of the two that covers izap-webhook-id, which closes a real gap: an attacker who captures one delivery can replay it inside your timestamp tolerance under a new izap-webhook-id, and a v1-only check still verifies because v1 never signed the id. There’s also no other generic signed identifier on the body to fall back on, and deduping on timestamp + signature doesn’t help either — both are freshly computed on every retry and resend of the same event.
Read the body as raw bytes before any JSON parsing your framework does automatically — most frameworks buffer and re-encode a parsed body, which no longer matches what was signed.

Legacy: v1 signature

izap-webhook-signature (v1=<hex-hmac>) is still sent on every delivery and there’s no plan to stop — existing integrations keep working unchanged. It does not cover izap-webhook-id, so on its own it can’t tell a replayed delivery under a new id from a genuine new one; prefer izap-webhook-signature-v2 for new integrations, and migrate existing ones when convenient.

Delivery and retries

  • Your endpoint gets 10 seconds to respond. Anything in 2xx counts as delivered.
  • 5xx and 429 responses are retried; any other 4xx is treated as a permanent rejection and isn’t retried — return 2xx only once you’ve durably accepted the event.
  • After 20 consecutive failures an endpoint is automatically disabled (enabled: false, with last_error set). Re-enable it once your receiver is healthy again — it doesn’t recover on its own.

Manage an endpoint

Rotating invalidates the previous secret immediately — there’s no overlap window, so update your verification code with the new secret before rotating in production. A resend replays the original stored bytes, so it verifies against the same payload the first attempt did.

Scaffold a receiver

izap-wizard webhook receiver writes a starting point that implements this contract for you.