Back to Blog
Reliability6 min readApril 21, 2026

Webhook Retry Logic: Provider Policies and Best Practices

When webhook delivery fails, providers retry — but each on their own schedule. Here's how Stripe, GitHub, Shopify, and others handle retries, and how to build an idempotent receiver.

W
WebhookWhisper Team
April 21, 2026

Webhook delivery isn't guaranteed on the first attempt. Networks fail, servers restart, deploys cause brief downtime. Providers know this — so they retry. Understanding retry behavior helps you design receivers that handle failures without double-processing or data loss.

Why Retries Happen

A delivery attempt fails when: your server returns a non-2xx HTTP status, your server doesn't respond within the timeout window (typically 5-30 seconds), or a network error prevents connection.

Provider Retry Schedules

ProviderMax AttemptsWindowStrategy
Stripe~153 daysExponential backoff
GitHub103 daysExponential backoff
Shopify1948 hoursIncreasing intervals
Twilio3~4 hoursLinear
SendGrid372 hoursExponential
PagerDutyNo retriesFire and forget

Idempotency: Your Safety Net

Because retries mean duplicate deliveries, your handler must be idempotent. Every provider includes an event ID in the payload — store it and check before processing.

async function handleWebhook(eventId, payload) {
  const exists = await db.query(
    'SELECT 1 FROM processed_events WHERE event_id = $1', [eventId]
  );
  if (exists.rows.length > 0) return;

  await db.transaction(async (tx) => {
    await tx.query('INSERT INTO processed_events (event_id) VALUES ($1)', [eventId]);
    await processEvent(payload, tx);
  });
}

Respond Quickly, Process Async

Return 200 immediately. If your handler takes 30 seconds before responding, you'll hit the timeout and the provider will retry even though you eventually succeeded.

Dead-Letter Queues

When all retries are exhausted, the event is permanently dropped. For critical events (payments, subscription cancellations), implement a dead-letter queue.

CREATE TABLE webhook_dlq (
  id SERIAL PRIMARY KEY,
  provider TEXT NOT NULL,
  event_id TEXT,
  payload JSONB,
  error TEXT,
  failed_at TIMESTAMPTZ DEFAULT now()
);

Summary

  • Return 2xx fast — don't let processing delay your response
  • Store event IDs and check before processing (idempotency)
  • Monitor your webhook endpoint — alert on sustained failures
  • Implement a dead-letter queue for critical events
#webhooks#retry#reliability#idempotency

Ready to test your webhooks?

Get a free HTTPS endpoint in under 5 seconds — no signup required.

Create Free Account
Webhook Retry Logic: Stripe, GitHub & Shopify (2026) | WebhookWhisper