Webhooks
Webhook retries and replay
Build an idempotent endpoint that handles automatic delivery retries and manual event replay.
What this guide covers
- Return a successful response only after durable acceptance.
- Deduplicate side effects with stable event IDs.
- Use dashboard delivery history to inspect attempts and replay eligible events.
Delivery success and failure
Any HTTP 2xx response marks an attempt successful. A non-2xx response, network failure, destination-safety failure, or 10-second timeout marks the attempt failed.
Yanola stores a bounded, defensively redacted preview of the latest endpoint response. Headers, credentials, endpoint identifiers, and full response bodies are not exposed by the public delivery API.
Automatic retry schedule
Yanola makes up to 12 attempts. The base delays after failed attempts are 1 minute, 5 minutes, 15 minutes, 30 minutes, 1 hour, 2 hours, 4 hours, 8 hours, 12 hours, 18 hours, and 24 hours.
Each delay receives deterministic ±20% jitter per delivery and attempt. nextAttemptAt is the authoritative scheduled time; do not calculate an exact retry timestamp from the base list.
| Status | Meaning |
|---|---|
pending | Waiting for its first attempt or a scheduled retry. |
delivering | A worker has claimed the current attempt. |
succeeded | An attempt returned HTTP 2xx. |
failed | All allowed attempts were exhausted or the endpoint was auto-disabled. |
canceled | A permanent platform control blocked delivery. |
Do not return 4xx to suppress retries after you have accepted an event. Persist the event and return 2xx; handle application rejection in your own durable workflow.
Endpoint auto-disable
By default, Yanola disables an enabled endpoint after 12 consecutive endpoint delivery failures. Successful delivery resets the endpoint's consecutive-failure state.
When an endpoint is auto-disabled, inspect the failure history, correct the destination or handler, and explicitly re-enable it. Disabled endpoints receive no new deliveries.
List and retrieve deliveries
GET /v1/webhook-deliveries and client.webhookDeliveries.list require webhooks:read. Filter by status, eventId, createdAfter, or createdBefore. Lists default to 20 and accept at most 100.
import { WhatsAppClient } from "@yanola/whatsapp"
const apiKey = process.env.YANOLA_API_KEY
if (!apiKey) {
throw new Error("Missing YANOLA_API_KEY")
}
const yanola = new WhatsAppClient({ apiKey })
const page = await yanola.webhookDeliveries.list({
eventId: "evt_...",
limit: 100,
})
for (const summary of page.data) {
const { data: delivery } =
await yanola.webhookDeliveries.retrieve(summary.id)
console.log({
id: delivery.id,
status: delivery.status,
attempts: delivery.attemptCount,
nextAttemptAt: delivery.nextAttemptAt,
responseStatus: delivery.responseStatus,
})
}Replay a failed delivery
POST /v1/webhook-deliveries/{deliveryId}/replay requires webhooks:write, accepts no request body, and can replay only a failed delivery whose retained event is still available.
Replay creates a new pending del_ ID for the same event, endpoint, and payload format. The new request includes yanola-replayed: true. It does not change the original failed delivery.
import { WhatsAppClient } from "@yanola/whatsapp"
const apiKey = process.env.YANOLA_API_KEY
if (!apiKey) {
throw new Error("Missing YANOLA_API_KEY")
}
const yanola = new WhatsAppClient({ apiKey })
const replay = await yanola.webhookDeliveries.replay({
deliveryId: "del_...",
idempotencyKey: "incident-1042-replay-1",
})
console.log(replay.id) // new del_ ID
console.log(replay.eventId) // original evt_ ID
console.log(replay.status) // "pending"
console.log(replay.replayed) // trueThe replay Idempotency-Key is optional but recommended for application retries. Use 8 to 255 visible characters and keep the same key for one logical replay request.
Deduplicate replayed events
A replay has a new delivery ID but the same event ID. Deduplicate business side effects by event ID, not delivery ID, and treat yanola-replayed as operational context.
If you intentionally want to re-run a business action, create an explicit application operation after inspecting the event; do not make webhook replay silently bypass your event deduplication rule.
Replay errors
| Status and code | Meaning | Action |
|---|---|---|
400 invalid_webhook_replay_request | A request body was supplied. | Send a bodyless POST. |
400 invalid_idempotency_key | The optional key is outside the 8-to-255-character contract. | Use one visible stable operation key. |
403 insufficient_scope | The key lacks webhooks:write. | Grant replay access intentionally; new keys exclude it by default. |
404 webhook_delivery_not_found | The delivery is missing or inaccessible in this key scope. | Check the del_ ID and environment. |
422 webhook_delivery_not_replayable | The source delivery is not failed. | Do not replay pending, delivering, succeeded, or canceled deliveries. |
422 webhook_delivery_event_unavailable | The retained source event is no longer available. | Reconstruct the application action through your own records. |
503 idempotency_unavailable / webhook replay unavailable | A required dependency is temporarily unavailable. | Honor retryable and Retry-After. |