Browse documentation

Webhooks

Webhook retries and replay

Build an idempotent endpoint that handles automatic delivery retries and manual event replay.

DevelopersAdvancedAvailable

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.

Webhook delivery statuses
StatusMeaning
pendingWaiting for its first attempt or a scheduled retry.
deliveringA worker has claimed the current attempt.
succeededAn attempt returned HTTP 2xx.
failedAll allowed attempts were exhausted or the endpoint was auto-disabled.
canceledA permanent platform control blocked delivery.

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.

Inspect one event's delivery attempts
ts
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.

Queue a replay
ts
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) // true

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

Webhook replay errors
Status and codeMeaningAction
400 invalid_webhook_replay_requestA request body was supplied.Send a bodyless POST.
400 invalid_idempotency_keyThe optional key is outside the 8-to-255-character contract.Use one visible stable operation key.
403 insufficient_scopeThe key lacks webhooks:write.Grant replay access intentionally; new keys exclude it by default.
404 webhook_delivery_not_foundThe delivery is missing or inaccessible in this key scope.Check the del_ ID and environment.
422 webhook_delivery_not_replayableThe source delivery is not failed.Do not replay pending, delivering, succeeded, or canceled deliveries.
422 webhook_delivery_event_unavailableThe retained source event is no longer available.Reconstruct the application action through your own records.
503 idempotency_unavailable / webhook replay unavailableA required dependency is temporarily unavailable.Honor retryable and Retry-After.