Terms
The webhook pages use the terms of Core concepts: webhook subscription, your endpoint, event, delivery, attempt, signing secret, probe and events feed.Subscribing
Create a subscription withPOST /v1/webhooks. The body
takes three fields:
The call returns
201 with the subscription and its signing secret. The subscription is active
immediately. description and active are ignored on create: set them with
PATCH /v1/webhooks/{id}.
Four rules apply to every webhook operation:
- A subscription belongs to your account. Every API key of the account reads and changes every subscription of the account.
- A line-scoped key is not restricted on webhook operations. It creates subscriptions for all lines, reads every delivery record and reads the events feed of every line of the account, message content included. Do not treat a line-scoped key as a boundary here.
- An
isnap_test_account key is not a separate test environment: it acts on the same account, lines and billing as anisnap_live_key. Both kinds of key see the same subscriptions, and an event carries no mark of the key that caused it. See Test keys. - A trial key cannot call a webhook operation: the API returns
401 unauthorized.
POST /v1/webhooks does not honor Idempotency-Key. The same request sent twice creates two
subscriptions, and each event is then delivered twice to that URL. Retry a write only on an
operation that honors Idempotency-Key, with the key of the first attempt. After a lost response,
list your subscriptions with GET /v1/webhooks before you
create one again. The API sets no limit on the number of subscriptions of an account, and two
subscriptions can use the same URL.
Give each subscription its own URL, for example with a path segment of your own. A delivery does not
carry the wh_ id of its subscription, so a receiver that serves several subscriptions on one URL
has to try each signing secret.
URL rules
The issue code is in
error.issues[0].code, and error.issues[0].path is ["url"]. The API sets no
restriction on the port, the path or the query string.
iSnap checks the host again before every attempt and every probe. If the URL then points to a
non-public address, no request is sent, the delivery ends failed, and the subscription is suspended
immediately: see Suspension.
Event filter
An entry ofevents is one of:
The families are
message, reaction, line, typing_indicator, trial, binding, pre_order,
macos_session and webhook. The event catalog lists the 37
event types.
- There is no default filter. A missing or empty
eventsreturns400 validation_failed. - An exact name that is not an event type returns
400 validation_failed. - A wildcard is accepted for any lowercase family name, including a family that does not exist. It then matches nothing: check the spelling of a wildcard yourself.
- The API sets no maximum number of entries, and accepts duplicates.
- Subscribing to
webhook.testorwebhook.*has no effect:webhook.testis sent only as a probe, whatever the filter.
Line filter
POST /v1/webhooks refuses line_ids: null with 400 validation_failed. On
PATCH /v1/webhooks/{id}, line_ids: null removes the line filter. A subscription without a line
filter reads back with line_ids: null, or with [] when you sent []: both mean all lines.
With a non-empty line_ids:
- Events that carry no line id never reach the subscription:
pre_order.cancelled,macos_session.rentedandmacos_session.released. The twomacos_session.*events stay out even when the session runs one of the listed lines. trial.*events, and the message, reaction and typing events of a trial, are tied to the line the trial runs on (pool_line_id). The subscription receives them only if it lists that line id.pre_order.fulfilledis tied to the line the pre-order produces. The pre-order does not give you that line id beforehand: receive pre-order events on a subscription without a line filter.
line_ followed by 25 letters or digits. An id that does
not exist, or the id of a line that is not yours, is accepted. It matches nothing, with one
exception: the pool_line_id of your trials matches the events of those trials, whoever owns the
pool line. The API sets no maximum number of line ids.
What your endpoint must do
- Serve HTTPS on a public host. The certificate must be one a standard HTTP client accepts. A self-signed or expired certificate is a failed attempt.
- Verify the signature on the raw body. Compute the signature over the bytes you received, before
any JSON parsing: see Verifying signatures. iSnap promises no
User-Agentvalue and no list of source addresses: a delivery is authenticated by its signature, and by nothing else. - Return a
2xxstatus within 10 seconds. Any status from200to299, received within 10 seconds, is a success. iSnap does not interpret the response body, but it keeps its first 1,024 characters in the delivery record, readable with any API key of your account for at least 72 hours, or until you delete the subscription: return an empty body. Every other outcome is a failed attempt, retried except after a410: see Retries. - Do not redirect. iSnap never follows a redirect. A
3xxresponse is a failed attempt. - Respond first, process later. Store the event, return
2xx, then do the work. A response that arrives after 10 seconds counts as a failure even if you processed the event, and the event is then delivered again. - Deduplicate, and do not rely on order. Deduplicate on
event_id. Delivery order is not guaranteed. Order events by the timestamps insidedata, not by arrival. See Duplicates and Order. - Accept what you do not handle. Return
2xxfor an event type you do not handle, and for a probe (webhook.test). Ignore response fields, enum values and event types you do not know.
What a delivery contains
A delivery is an HTTPPOST to your URL. The body is one event, as compact JSON in UTF-8: see the
envelope. The API sets no maximum size for the body.
The three
webhook-* headers carry the signature. The X-iSnap-* headers are informational and are
not signed: read the event type and the event_id from the verified body.
Events can carry personal data: handles (phone numbers and email addresses), message text, names and
attachment file names. Each event page says which fields.
Subscribe only to the event types you need. Do not copy personal data into logs, tickets or other
tools. Refer to a contact by an id of your own.
Lines that change owner
A line changes owner when you transfer it to another account, or when it returns to the marketplace and another account gets it. The events about its earlier messages follow the line: they go to the account that owns the line when the event occurs:message.sent,message.delivered,message.read,message.failedandmessage.fallback_triggeredabout a message that an earlier owner sent, each with the full message: its text, its handles, its attachment file names and itsmetadata;reaction.receivedandreaction.removedabout a reaction of a contact to an earlier message;- the
message.readevents of a mark-read: aPOST /v1/chats/{chat_id}/readby the next owner also marks read the inbound messages that an earlier owner had not marked read, and sends each of them in full.
- a
message.*event whosedata.message.created_atis earlier than the time the line became yours; - a
reaction.*event whoseparent_message_idis not a message that you sent or received on the line.
reserve or assign call, or, for a
transfer, the time that the sender gives you. No event announces a transfer, and activated_at takes
the time of the transfer only on the line of a wholesale partner: see
Transfer. With a time later than the real one, the first rule
drops your own events of the interval.
With the exact time, these rules never drop message.received, or message.sent with
origin: "device": both announce a message that the line records while it is yours.
Subscription states
The API shows
active only. A suspended subscription still reads active: true, and no field says
that a subscription is suspended, since when, or why.
Suspension explains how to detect it.
A subscription is returned in two shapes:
failure_count, last_success_at and last_failure_at are described in
Suspension. GET /v1/webhooks returns every subscription of the
account in one response, newest first, without pages and without a filter.
Example: create a subscription
Replace the URL with the URL of your endpoint. The samples do not retry: the operation does not honorIdempotency-Key. The response contains the signing secret: run the command where its output
is not logged, and store the secret in your secret manager.
whsec_ followed by 64 lowercase hexadecimal characters: 70 characters in all.
Keep it in the environment of your receiver as ISNAP_WEBHOOK_SECRET.
Next steps
Event catalog
The envelope, the 37 event types, and what events do not announce.
Verifying signatures
The three signature headers, code in three languages, and a test vector.
Delivery guarantees
Duplicates, order, retries, suspension, replays, secret rotation and retention.
Events feed
The events feed, and a scheduled poll as a safety net.