Webhooks
Register endpoints, verify deliveries, rotate secrets, and replay safely.
Register a destination with POST /v1/webhook_endpoints.
The created response returns signing-secret material once; store it immediately
in a secret manager. Endpoint reads expose configuration and health, never the
secret value.
Verify every request against the exact raw body before parsing JSON. Respond quickly after durable acceptance in your system, and process asynchronously. Use the event ID as the deduplication key.
Deliveries to one endpoint and chat are sent in the order their events were
recorded. A message.received for a message with attachments can be recorded
once its attachments are captured or have failed, or ten minutes after the
message arrived, so it can follow deliveries for later messages of the same
chat with a lower data.seq_from. Never discard it as stale: deduplicate by
event ID and order the chat by data.seq_from. See
Events and recovery → Messages with attachments.
GET /v1/webhook_deliveries exposes delivery attempts
and outcomes. If a bounded historical range needs redelivery, create a replay
under the endpoint and poll that replay’s status. Replay creation has its own
idempotency key; use a stable key when retrying the same replay request.
Adding a signing secret starts a rotation window. Deploy verification for both active secrets before retiring the old one. Disabling an endpoint is a cutoff, not a pause: events produced while disabled are not silently queued for later.
Scope an endpoint to specific numbers
Section titled “Scope an endpoint to specific numbers”By default an endpoint receives events for every number the account has now
and adds later; endpoint reads report phone_number_ids as null. To narrow
it, pass phone_number_ids on POST /v1/webhook_endpoints
or PATCH /v1/webhook_endpoints/{endpoint}: up to 100 exact ids
from GET /v1/phone_numbers (see the
phone numbers guide). The endpoint
then receives only the chat events of those numbers. Account-wide events that
carry no phone number reach only unscoped endpoints, so keep one unscoped
endpoint if you need them.
An id the account cannot use — unknown, another account’s, the other mode’s,
or not an active number of this account — is refused with HTTP 422 and
error.code 1041. The pointer is
the list, /phone_number_ids, never the id, so a refusal does not say whose
number it is; only a malformed id (empty, or over 128 bytes) is pointered at
its own index, /phone_number_ids/{i}. Either way nothing is created or
changed.
An update replaces the list outright; an empty array restores every current and future number. The change affects only events fanned out after the update: deliveries already queued keep their destination, and a replay accepted earlier keeps the scope it was accepted under. Field details are on the create and update reference pages.