What is guaranteed
- At least once, while the subscription stays active. iSnap sends a recorded event to each
subscription it matched until your endpoint returns
2xxor410, or the attempts run out. A delivery can end before its attempts run out: when the subscription is turned off or deleted, when its URL is refused, when iSnap would resend it after a suspension or a stall more than 24 hours after its creation, and when it waits or stalls again after iSnap resent it 3 times. See Success and failure and Suspension. A delivery that endsfailedcan be replayed while its record exists, and its event read from the events feed. An event can be delivered more than once. - An event is recorded only if it matches an active subscription when it occurs. An event that matches no active subscription at that moment is never delivered and never listed.
- An event can be lost: see What events do not announce.
- No delay is promised. The API does not bound the delay between a change and the first attempt.
Duplicates
Deduplicate onevent_id. It is the same value as the webhook-id header, and it is the same on
every attempt, on a replay, and for every subscription that receives the event.
The same event reaches you more than once when:
- your endpoint processed a request but returned its response after 10 seconds;
- iSnap resends a delivery after a suspension ends, or after it recovers a delivery that stalled;
- you replay a delivery;
- two of your subscriptions have the same URL and both match the event;
- a line that is not a BYOD line stays offline: its durable
line.offlineis delivered again with the sameevent_id.
Order
Delivery order is not guaranteed. Order events by the timestamps insidedata, not by arrival. For
message events, apply
Building the status from events.
Deliveries are sent concurrently and retried independently, so events can arrive in any order, two
events about the same message included. The created_at of the envelope is the time iSnap recorded
the event, not the time the change happened.
Success and failure
An attempt succeeds when your endpoint returns a status from200 to 299 within 10 seconds. 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: return an empty
body.
Every other outcome is a failed attempt:
- a
3xxstatus: iSnap never follows a redirect; - a
4xxstatus,400,401,404and429included; - a
5xxstatus; - no response within 10 seconds;
- a host that does not resolve, a connection error, or a TLS error.
410. iSnap ignores a Retry-After header.
Four outcomes end a delivery without another attempt:
Retries
A retry schedule has at most 7 attempts. Each delay is counted from the previous attempt. A delivery that iSnap resends after a suspension or a stall starts a new schedule, so one delivery can have more than 7 attempts in all.
The delays add up to 14 hours 36 minutes 30 seconds, so the 7th attempt starts at least that long
after the first.
- Every attempt carries the same
webhook-idand a newwebhook-timestampand signature. - A new
urlset withPATCH /v1/webhooks/{id}applies to every request not sent before the change, pending retries included. - When the 7th attempt fails, the delivery ends
failedand thefailure_countof the subscription rises by 1. - A delivery that stays
pendingwith no attempt scheduled is resent by iSnap, with a full new schedule, if it was created less than 24 hours earlier. Past 24 hours it endsfailed. It can also endfailedwhen it stalls again after iSnap resent it 3 times. In both cases itsresponse_body_snippetstarts withabandoned:, and you can replay it while its record exists.
Suspension
iSnap suspends a subscription in two cases:failure_count counts deliveries, not attempts. One successful delivery, or one successful probe,
resets it to 0. last_success_at is the time of the last 2xx, from a delivery or a probe.
last_failure_at is the time of the last delivery that used all its attempts, the last 410, or
the last refused URL. Both are null until the first such outcome. These three fields are returned
by POST /v1/webhooks and
GET /v1/webhooks only.
While a subscription is suspended
- A delivery that was already created waits: no request is sent, its
statusstayspending, and itsnext_retry_atbecomesnull. - iSnap sends a probe: a signed
webhook.testrequest. Two probes are at least 15 minutes apart. The first is sent within 15 minutes of the suspension, unless the subscription was already probed in the 15 minutes before it. - A subscription whose URL is refused is not probed until the URL passes the check again: after you
change it with
PATCH /v1/webhooks/{id}, or after its host resolves to a public address again.
How a suspension ends
A2xx response to a probe, received within 10 seconds, resumes the subscription: failure_count
returns to 0 and last_success_at is set. Any other response, a redirect, a timeout or a network
error leaves it suspended.
No operation resumes a suspended subscription. PATCH /v1/webhooks/{id} with active: true, and a
change of URL, do not end a suspension: only a successful probe does.
After the subscription resumes, iSnap resends each delivery that waited, with a full new retry
schedule that starts at attempt 1. The API does not bound the delay between the resume and the
resend. There are two exceptions:
- a delivery created more than 24 hours before the time iSnap would resend it ends
failed, withresponse_body_snippetset toabandoned: older than 24h; - a delivery that iSnap already resent 3 times can end
failedinstead, withresponse_body_snippetset toabandoned: rescued 3 times.
How to detect a suspension
No field says that a subscription is suspended, and no event announces it. A suspended subscription still readsactive: true. Use these signs:
To avoid a suspension, monitor the failures of your endpoint, and check that the host of your URL
keeps resolving to a public address.
A 410 response
A410 response to a delivery ends that delivery failed and sets the subscription to
active: false immediately. It does not change failure_count.
An inactive subscription is not probed. Turn it on again with
PATCH /v1/webhooks/{id} and active: true. The events
that occurred while it was inactive are lost for it.
A 410 response to a probe has no special effect: the subscription stays suspended.
Turning off and changing a subscription
PATCH /v1/webhooks/{id} accepts any subset of url,
events, line_ids, description and active. A body with none of them returns
400 validation_failed.
Replays
POST /v1/webhooks/{id}/deliveries/{delivery_id}/replay
creates a new delivery of the event of a finished delivery. A replay targets a delivery of a
subscription, not an event id.
A replay records a new delivery of the event at the time of the replay. The events feed lists an
event at the time of its earliest delivery inside your window: while the first delivery is inside the
window, a replay does not move the event; otherwise the event is listed at the time of the replay,
with its original
created_at. Deduplicate on event_id.
The sample replays every event of the last 24 hours whose latest delivery failed. The list returns
the newest record first, so the first record seen for an event_id is its latest delivery.
Idempotency-Key: a call sent twice creates two deliveries of the same
event. Your deduplication on event_id absorbs them.
Secret rotation
POST /v1/webhooks/{id}/rotate-secret replaces the
signing secret and returns 200 with id, secret and old_secret_expires_at.
- The rotation takes effect immediately. Every request sent after it is signed with the new secret only: first attempts, retries of deliveries created before the rotation, replays and probes.
- There is no overlap. After a rotation iSnap never signs with the previous secret, and it never sends two signatures.
old_secret_expires_atis informational. It is the time of the rotation plus 5 minutes, and it has no effect on what iSnap sends.- The new secret is returned once. No other operation returns it.
1
Make your receiver accept a list of secrets
Verify each request against every secret of the list, and accept it when one matches. With the
SDK, pass an array as the third argument of
verifyWebhook.2
Rotate, and store the new secret
Call the operation once. Do not retry it blindly: it does not honor
Idempotency-Key, and a
second call replaces the secret again. If you lose the response, rotate again.3
Add the new secret to the list immediately
Between the rotation and this step, your receiver cannot verify the requests iSnap sends. Return
a status outside
2xx for a signature you cannot verify: iSnap retries the delivery on the
schedule.4
Remove the previous secret after old_secret_expires_at
Keeping the previous secret until then covers a request that iSnap signed just before the
rotation and that is still in transit.
Delivery records
GET /v1/webhooks/{id}/deliveries lists the
delivery records of a subscription. A record describes one delivery: one event for one subscription.
It is updated at each attempt, so it shows the latest attempt, not a history of attempts.
In four cases where no request was sent,
response_body_snippet contains a note written by iSnap,
not a response of your endpoint:
A delivery whose URL was refused, or whose host did not resolve, has no note: its
response_body_snippet is null.
The list takes these parameters:
Records are returned newest first.
has_more says whether another page exists: next_cursor is
then a string, and null otherwise. A malformed cursor returns 400 invalid_cursor. The cursor
does not carry the filters: send the same filters on every page. See
Cursors.
A probe creates no delivery record.
request_body_snippet can contain personal data: the beginning of an event, with message text and
handles. Do not copy delivery records into logs, tickets or other tools.
Retention
During these periods, anyone who has an API key of your account, a line-scoped key included, can read
the full content of each event through the events feed, and the first 1,024 characters of each
request body and of each response of your endpoint through the delivery list.
Deleting a subscription
DELETE /v1/webhooks/{id} returns 204 with no body.