At a glance
Success and retries
A delivery succeeds when your endpoint returns any2xx status within 10 seconds. Otherwise Blujay retries it automatically, up to 10 attempts in total over about 25 minutes:
Each wait varies by up to 20% so retries don’t arrive in bursts, and can be longer when Blujay is under heavy load.
What your response means to Blujay
Every retry carries the same
event_id and body, with a fresh signature. If your endpoint is down for longer than the retry window, replay the events you missed once it is back.
Redirects
If your endpoint answers with a redirect (301, 302, 303, 307 or 308 and a Location header), Blujay follows it, up to 3 redirects per attempt:
- Blujay sends the same
POST, with the full body and signature headers, to each new address. It never turns the request into an emptyGET. - Each step gets its own 10 seconds.
- Every address must be public HTTPS. A redirect to
http://or to a private address fails the attempt withWEBHOOK_URL_INVALIDorWEBHOOK_URL_UNSAFE. See Where Blujay won’t deliver. - Too many redirects (
WEBHOOK_REDIRECT_LIMIT_EXCEEDED) or a brokenLocation(WEBHOOK_REDIRECT_INVALID) isn’t retried, because retrying wouldn’t change the result. Fix the URL, then replay the event.
error_code values from the deliveries list below, not API error codes; they have no error page.
Redirects usually mean the subscription URL is slightly off: example.com instead of www.example.com, or a missing or extra trailing /. To find the address your server really answers on, run this. It prints the final URL after any redirects:
url with PATCH /v1/webhook-subscriptions/{subscription_id} so deliveries skip the redirect.
Inspect delivery attempts
next_retry_at will be retried; it isn’t final. Add event_id=evt_abc123 to see every attempt for one event; cursor and limit page the list. You can see the same log in Blujay under API → Webhooks.
Find the event for a message
There is no endpoint that lists events, and no lookup from amessage_id to its events. The event_id arrives in the webhook-id header and the event_id field of each delivery, so store it with the event. If you never received the event, page the deliveries list for the time window, which shows each event_id and event_type but not the message, and replay the events in that window; each replay carries the full body.
Replay an event
After you fix your endpoint, send an event again:202 Accepted and makes one more delivery attempt, recorded with delivery_mode: "replay". Check the deliveries list for its result.
- The replay has the original
event_idand body, in the version the event was created with. - If automatic retries for the event are still running, the replay waits for them to finish first.
- A replay that fails isn’t retried automatically. Replay it again when you’re ready.
- The subscription must be active and still include the event’s type and phone number, or you get
409WEBHOOK_SUBSCRIPTION_INACTIVEorWEBHOOK_PHONE_NUMBER_EXCLUDED. - Reuse the same
Idempotency-Keyonly to retry the same replay request. A different key sends the event again. - Events stay replayable for at least the full retry window. Blujay doesn’t publish a longer retention guarantee, so replay what you missed as soon as your endpoint is back.
- Only events Blujay recorded can be replayed. While a subscription is disabled, no events are recorded for it. See Webhooks.
Limitations
- The retry schedule is fixed. You can’t change the number of attempts or the waits, and
Retry-Afteron a429is ignored. - Replay is one event at a time, to the subscription that recorded it. There is no bulk replay and no replay to a different subscription.
- Events from a disabled subscription are never recorded, so they can’t be replayed. Pausing a subscription loses events.
- There is no events endpoint and no message-to-event lookup. Store
event_idwith every event you receive. - Attempt
error_codevalues are not API error codes. Only the API errors above link to an error page.
Common mistakes
Next steps
Verify signatures
Every attempt is signed again.
Events
Every event type and its payload.