DevelopmentFramework12 min readPublished May 28, 2026

Duplicates and gaps are possible · idempotency is yours to build · per-provider decision matrix inside

Webhook Reliability: Idempotency & Retry Reference

Webhook retries can duplicate events, and finite retry budgets can leave gaps. Build around durable acceptance, idempotent effects, bounded retries, and reconciliation. This reference separates provider behavior from the engineering choices you control.

DA
Digital Applied Team
Senior engineers · Published May 28, 2026
PublishedMay 28, 2026
UpdatedOctober 4, 2026
Read time12 min
SourcesStripe, Shopify, AWS, Svix docs
Stripe live-mode retry
3days
exponential backoff
Shopify retry budget
8×
over 4 hours
Admin API subscriptions
Stripe replay tolerance
5min
Stripe signature tolerance
Signature algorithm
HMAC
SHA-256, constant-time compare

Webhook reliability starts with two uncomfortable possibilities: the same event can arrive again, and an event can exhaust its delivery budget without reaching your application. A successful HTTP response also says nothing about whether the eventual business operation succeeded. The consumer needs a durable record of accepted work and an explicit rule for safely repeating that work.

A timeout after fulfillment creates an ambiguous result: the sender sees failure while the receiver may already have shipped an order. Acknowledging before persistence creates the opposite problem: the sender sees success while a process crash erases the only copy. Design and test both boundaries. Signature verification, duplicate handling, retries, and dead-letter queues each solve a different part of the problem; none supplies an end-to-end guarantee by itself.

This reference compares HTTPS webhook delivery with the Amazon SQS Standard queue layer, then connects them through a durable inbox pattern. Provider facts were checked on October 4, 2026. Configuration examples are illustrative engineering choices, not measured benchmarks or vendor defaults. The original publication date remains May 28, 2026.

Key takeaways
  1. 01
    A finite retry budget can leave a delivery gap.Plan for duplicates and exhausted deliveries; do not infer guaranteed eventual arrival from a retry policy.
  2. 02
    Commit accepted work before acknowledging it.Atomically deduplicate and persist an inbox record. Return success only after that commit, then let a recoverable worker process it.
  3. 03
    Deduplication needs an explicit effect boundary.An event ledger covers repeated deliveries. Business-operation keys, transactions, or downstream idempotency cover repeated side effects.
  4. 04
    Retention must include replay.Size the ledger for automatic retries, manual resend, internal redrive, and reconciliation; a retry-window TTL alone is incomplete.
  5. 05
    Verify the actual provider protocol.Preserve raw bytes, select the configured endpoint secret, and use the official verifier for header formats, rotation, and signed timestamps.

01 — Scope the GuaranteeDelivery success and business success are different.

Treat an acknowledgement as a statement about one boundary: this receiver has durably accepted responsibility for the event. It should not claim that a payment, inventory update, or email already happened. Keep delivery state, processing state, and business outcome separate in logs and operator tools. Otherwise an HTTP success graph can hide a growing backlog of unprocessed orders.

Stripe documents duplicate webhook events. Shopify documents duplicate deliveries and a finite retry budget. Svix marks delivery as failed after its retry schedule is exhausted. SQS Standard explicitly offers at-least-once queue delivery; that is a queue contract, not proof that every upstream webhook reaches the queue. The four systems belong at different points in the pipeline, so their guarantees should not be flattened into one slogan.

Idempotency and polling need an explicit effect boundary and recovery assumptions. An atomic transaction can suppress repeated changes inside its own database boundary. Once the operation crosses into another service, a crash can occur after that service acts but before your ledger records completion. Solve that ambiguity with a stable downstream operation key, an outcome lookup, or a compensating workflow.

Name the boundary
Write acceptance criteria for the actual effect: a fulfillment record is created once per order transition, or a downstream payment request reuses one operation key until its outcome is known. A transport event ID and a business-operation ID can serve different purposes. This makes a guarantee reviewable without promising a perfect network.

02 — Provider BehaviorCompare the delivery contracts, then choose your retention.

The table records current documented behavior, with SQS shown as infrastructure after ingress. Stripe’s Receive Stripe events documentation, Shopify’s Verify webhook deliveries documentation, and Svix’s Retry Schedule and Manual Verification documentation were checked on October 4, 2026. Their automatic retry limits do not define a safe universal ledger expiry. Keep a separate policy for operator replay and recovery.

Provider behavior checked October 4, 2026. SQS Standard is a queue, not an HTTPS webhook sender.
DimensionStripeShopify HTTPSSvixSQS Standard
Automatic delivery budgetUp to 3 days live; 3 sandbox retries over a few hours8 retries over 4 hours8 scheduled attempts including the initial attemptMessage retention, visibility, and redrive policy
Attempt timingExponential backoffProvider-managed retry scheduleImmediate; then 5s, 5m, 30m, 2h, 5h, 10h, 10h after preceding failureVisible again if not deleted; not an HTTP backoff schedule
Duplicate identityEvent id; business duplicate detection is separateX-Shopify-Webhook-Id per delivery; X-Shopify-Event-Id correlates an actionsvix-id; optional white-label webhook-idPreserve upstream event/business ID; do not rely on a new enqueue ID
SignatureStripe-Signature, timestamp plus raw bodyX-Shopify-Hmac-SHA256, raw body digestsvix-signature, ID plus timestamp plus raw bodyAWS API authentication; no incoming-webhook signature header
Failure outcomeInspect failed deliveries and recoverAdmin-API-configured subscription deletion after 8 consecutive failuresFailed message; prolonged failure can disable endpointDLQ if configured; otherwise retention expiry
Consumer retention decisionInclude manual resend and internal replayInclude subscription recovery and reconciliationInclude manual replay; schedule exceeds 24hInclude source/DLQ retention and business recovery horizon

The Svix intervals add to 27 hours, 35 minutes, and 5 seconds before allowing for request durations. They are relative delays after each preceding failure, not timestamps measured from the first attempt. A 24-hour deduplication cache therefore expires too early even for this automatic schedule. Do not replace that mistake with another fixed TTL: inspect replay availability and the lifetime of the business operation.

Stripe permits manual resend through its Dashboard for up to 15 days and through its CLI for up to 30 days after event creation. Those controls can overlap automatic retries. The operational lesson is to retain or reconstruct processed-operation evidence for the replay horizon you actually support. A short-lived cache may accelerate lookups, but it should not be the only durable record of a financial effect.

03 — Durable OwnershipDeduplicate acceptance, then protect the effect.

An unsafe pattern is to mark an event as seen and then enqueue it in a separate operation. If the enqueue fails, a retry sees the marker and gets discarded; work is permanently lost. Reversing the operations can enqueue duplicates. Use an inbox table as the durable work queue, or commit an inbox row and an outbox row in the same transaction. A relay can publish the outbox later and retry after a crash.

Define a unique key that includes the provider, trusted account or tenant scope, and event ID. Resolve the tenant from the verified endpoint configuration and authenticated payload; do not trust an arbitrary tenant header. The persistence adapter must atomically return either inserted or already present, with an existing row always representing retained work or completed processing. A duplicate of pending work is safe to acknowledge because the worker can still discover it.

A Redis lock alone is not the same contract. Expiry, eviction, persistence settings, and a worker crash can reopen the operation or suppress unfinished work. For transactional state, keep the idempotency ledger and the local change in the same transaction. For an external effect, retain the stable operation key across retries and uncertain outcomes; do not generate a fresh payment or fulfillment key after a timeout.

Stripe’s Idempotent requests API reference describes a separate sender-side feature: keys can be up to 255 characters; the first saved result, including a 500 response, is reused. Keys become eligible for pruning after at least 24 hours. That policy concerns requests you send to Stripe and is not the retention contract for webhook events you receive.

Two levels of duplicate
A repeated transport event is different from two legitimate event objects describing one business outcome. Use the event ID for the inbox, and define an operation key around the specific effect and transition. Do not deduplicate every update to an object forever merely because the object ID matches.

04 — Bound the LoadBackoff and jitter protect the recovering dependency.

A local worker retry policy and a provider’s delivery policy are separate loops. If both retry aggressively while a downstream dependency fails, traffic can multiply. Give the worker a bounded attempt budget and a deadline, record the next eligible attempt durably, and move unresolved work into a recoverable failure state when the budget ends. A retry must reuse the same business-operation key.

For a sender or worker you control, capped exponential backoff makes later attempts less frequent. Jitter spreads otherwise synchronized requests. The AWS Architecture Blog comparison of jitter strategies explains full jitter, equal jitter, and decorrelated jitter. Choose from workload evidence rather than presenting one delay range as an industry norm. A cap and total deadline still matter with decorrelated jitter.

Strategy
Full jitter
random(0, capped_delay)

Randomize across the capped interval. A reasonable starting choice when many workers can fail together; test the load distribution and deadline.

Evaluate first
Strategy
Equal jitter
delay/2 + random(0, delay/2)

Keep a minimum spacing and randomize the remainder. Use when near-immediate retries would overload the dependency.

Minimum spacing
Strategy
Decorrelated jitter
previous delay + randomization

Use the previous delay to choose the next interval, with an explicit cap. The policy still needs finite attempts and a recovery path.

Bound the growth

Do not retry an ambiguous side effect merely because a network error looks transient. First determine whether the downstream service supports an idempotency key or an outcome query. If it supports neither, a blind retry may repeat the effect; suspend and investigate or reconcile. Retryability is a property of the operation and its failure, not just of an HTTP status class.

05 — Response PolicySeparate provider behavior from your sender policy.

A receiver cannot stop a managed provider from retrying by choosing a status code that a generic guide calls permanent. Stripe treats redirects as failed deliveries; Svix treats every non-2xx response as failure unless its documented delivery-control mechanism applies. Shopify treats responses outside the 200 range as errors. Inspect the actual provider contract instead of assuming that all 4xx responses stop retries.

The following is an illustrative decision tree for an outbound sender you own. It is not a prediction of those providers’ retry schedules. A 429 response can contain Retry-After; if your sender supports it, parse seconds or an HTTP date, enforce bounds, and respect the endpoint’s rate limit. Keep retry budgets finite even when the response asks for a long wait.

Illustrative outbound policy, subject to endpoint contract and operation safety.
ResultSender decisionRequired action
2xxFinish this delivery attemptReceiver has durably accepted work; processing can still fail later
3xxConfiguration failure by defaultUse the registered final URL; avoid forwarding secrets to an untrusted redirect
400 / 401 / 403 / 404 / 410Stop automatic attempts pending repairAlert and correct payload, credentials, permissions, or destination
408 / 429Potential retry within budgetConfirm idempotent operation; honor valid bounded Retry-After where supported
5xx / connection failurePotential retry; outcome may be unknownUse idempotency or query outcome before repeating a side effect

Scope concurrency and circuit breakers per endpoint or tenant so one customer’s failure does not stall everyone. A breaker can pause attempts while leaving durable work pending. When it probes recovery, send bounded traffic and resume gradually. Choose failure thresholds and cooldowns from error rates and service objectives. Test recovery behavior with a bounded probe before allowing the full pending backlog to resume.

06 — Failure RecoveryA dead-letter queue preserves investigable work.

A DLQ isolates messages that cannot be processed within the configured retry budget. Amazon SQS uses a redrive policy with maxReceiveCount; its dead-letter queue guide advises allowing enough attempts for transient failures. It does not prescribe a universal minimum of three. Select the budget from processing duration, visibility timeout, failure modes, and your recovery deadline.

For Standard queues, retention expiration remains anchored to original enqueue time after movement to the DLQ. The DLQ age metric instead reflects time since that movement. FIFO resets the enqueue timestamp when moved. A DLQ can also break the ordering required by a FIFO workflow. Account for those different semantics before treating a metric or redrive as a correctness guarantee.

The SQS queue attribute reference limits MessageRetentionPeriod to 14 days and documents a 4-day default. A 30-day SQS DLQ retention setting is not available. If recovery requires longer storage, design a separate protected archive and replay process with its own data-retention policy. Retention is a ceiling on recoverability, not proof that an operator will act in time.

Detection
Alert on business impact
SLOdriven

Any failed money or fulfillment operation may merit immediate attention. Set depth and age thresholds for the workload; ten events and one hour are not universal defaults.

Own the response
Recovery
Replay with the original key
IDstable

Preserve provider event identity and the business-operation key across redrive. Examine uncertain effects before retrying them.

Inspect before replay
SQS retention
Budget time before expiry
14days max

Choose source and DLQ retention together. Standard messages retain their original expiration clock; arrange an archive if the recovery horizon is longer.

Documented service limit

A replay tool should show validation failures, attempt history, tenant scope, effect outcome, and who authorized the recovery. Rate-limit redrive so a recovered dependency is not overwhelmed. A completed ledger can suppress an already finished effect; a pending or failed row must remain eligible for recovery. If expiry deletes that evidence, replay is no longer automatically safe. The background job queue patterns reference provides broader worker-system context.

07 — Trust the BytesVerify the actual protocol before parsing.

Preserve the exact request bytes before JSON parsing. Stripe’s signature troubleshooting documentation explains how body mutation breaks verification; use its official library and the endpoint signing secret. A secret from a CLI listener differs from a Dashboard endpoint secret. Do not choose verification keys using untrusted request fields, and do not log secrets or complete sensitive payloads.

Shopify uses a base64 HMAC-SHA256 digest over the raw body. Svix signs the message ID, timestamp, and raw body together and can send multiple versioned signatures. These are different protocols even though they use related cryptography. Use the provider SDK rather than reusing a generic HMAC function across them. Signature verification authenticates the signed material; it does not automatically establish the trust of every unsigned header.

For a manual implementation, validate input encoding and signature length before constant-time comparison. Node’s timingSafeEqual documentation requires equal-length inputs and warns that surrounding code can still introduce timing leaks. A freshness check needs an authenticated timestamp and a synchronized clock. Stripe’s documented default tolerance is 5 minutes; do not disable tolerance to make an old captured request pass.

Shopify’s X-Shopify-Triggered-At can inform payload-age diagnostics, but its body HMAC does not authenticate that header. It is not a substitute for signed replay protection, and delayed legitimate delivery should not be rejected solely because an unsigned time looks old. Retained business-operation evidence protects against repeated effects even when a valid request can be replayed.

Rotation and recovery
Test key rotation, multiple accepted signatures, malformed headers, changed raw bytes, and stale signed timestamps with the provider’s verifier. Replay tools should create a fresh authorized internal attempt rather than weakening the public endpoint’s authentication rules to accept an old signed request.

08 — Commit Then AcknowledgeAccept durably, then process asynchronously.

Keep the request path short: authenticate the raw body, validate the supported event envelope and trusted account context, commit an inbox row, then return the provider’s success response. Shopify documents a 1-second connection timeout and 5-second whole-request timeout. Use the actual endpoint budget when measuring ingress latency; there is no universal five-to-fifteen-second deadline or unmeasured throughput guarantee.

The example below is framework-independent JavaScript for an asynchronous notification endpoint. It assumes a verifier adapter that throws on invalid authentication and an inbox adapter with an atomic insert-if-absent contract. It deliberately has no HTTP server, database implementation, or payment call. The inbox is the durable queue: a worker must recover pending records. Adapter tests alone do not prove production durability or distributed concurrency.

// Illustrative adapters; no production server or database.
export async function accept(rawBody, headers, config, verify, inbox) {
  let event;
  try {
    event = await verify(rawBody, headers, config);
  } catch {
    return { status: 400 };
  }
  if (!event || typeof event.id !== "string" || !event.id.trim()) {
    return { status: 400 };
  }
  const key = JSON.stringify([config.provider, config.account, event.id]);
  try {
    const result = await inbox.insertIfAbsent(key, event);
    if (result !== "inserted" && result !== "present") {
      throw new Error("Unexpected persistence result");
    }
    return { status: 200 };
  } catch {
    return { status: 503 };
  }
}

Here, present means the original inbox record is still durably recoverable or completed. An implementation that stores a transient lock and calls it present violates the contract. If the commit succeeds but its acknowledgement is lost, returning failure is acceptable: the provider retry finds the same row. If persistence fails before commit, the endpoint returns failure and leaves the next delivery eligible.

The processing side needs a second explicit contract. In one local transaction, update application state and mark processing complete. If another system performs the effect, use an outbox plus a stable downstream idempotency key or an outcome query. A worker lease prevents competing execution but is not evidence that the effect completed. Heartbeats, lease expiry, and recovery must retain work without granting a new business identity.

Test concurrent duplicate acceptance, distinct account scopes, persistence failure, commit-with-lost-response, and recovery of pending work. Test a crash after a downstream effect but before completion is recorded. The latter cannot be fixed by the ingress function alone. These boundaries are central to custom web and application development and CRM automation integrations; define them before selecting a gateway.

09 — State ReconciliationRecover gaps without inventing order.

Stripe does not guarantee event order. Treat an event as a notification to inspect the relevant state when that suits the workflow. A current-resource fetch is useful for converging a local projection, but cannot reconstruct every historical transition. Audit workflows may need the immutable event record as well as the current resource. Serialize or version updates per entity so two workers cannot overwrite newer state with older results.

A reconciliation job can compare provider records against your ledger using persisted checkpoints, overlapping retrieval windows, pagination, and operation keys. It reduces gaps only when the API exposes the necessary data, the credentials remain authorized, and the job actually finishes. Alert on checkpoint lag and failed pages. A scheduled poll by itself is not a guarantee that nothing is permanently lost.

The CloudEvents 1.0.2 specification defines an event envelope, not delivery or authentication guarantees. Its event identity is scoped by source plus id. Optional time metadata does not create a global sequence. Translating a webhook into that envelope can simplify downstream handling, but retain the original provider identity and provenance rather than assuming a normalized field solves deduplication.

Notifications
Webhook alone

Suitable when a missing update has limited consequences and the product explicitly tolerates that gap. Still authenticate and make repeated processing safe.

Document the loss tolerance
Material outcomes
Webhook plus reconciliation

Keep a durable ledger, recover exhausted deliveries, and reconcile against accessible provider state. Monitor completeness; polling cannot recover data the API does not expose.

Use a measured recovery objective
Ordered work
FIFO within its scope

SQS FIFO send deduplication covers a five-minute interval and ordering is scoped to a message group. Neither proves that an external effect happens once; DLQ isolation can interrupt ordering.

Define the entity boundary

For the FIFO distinction, see AWS’s exactly-once processing explanation. Keep the application ledger even when the queue deduplicates sends. A manual recovery outside the queue’s deduplication interval still needs an operation identity. Related REST API design patterns help specify these contracts at service boundaries.

10 — Operating DisciplineMake reliability a tested boundary.

A reviewable integration contract

Commit accepted work, protect effects, and rehearse recovery.

Start with raw-byte authentication and trusted endpoint scope. Atomically retain the event before acknowledging it. Let recoverable workers perform the effect with a durable operation key. Give retries finite budgets and a monitored recovery path. Set ledger and queue retention from the replay horizon and business obligations, not an isolated provider number.

Maintain the provider matrix with a checked date and a named owner. Add tests for the uncomfortable states: the response is lost, persistence fails, workers race, a downstream effect succeeds before a crash, and an old event returns through redrive. Local mocks can validate control flow; only adapter and integration evidence can establish the persistence boundary in a real deployment.

Measure ingress acknowledgement latency, pending-work age, processing failures, reconciliation lag, and unresolved effect outcomes separately. The result is an operating contract your team can inspect and improve. It is stronger than an exactly-once slogan because it names the assumptions under which repeated deliveries remain safe and missing work can be recovered.

Make your event-driven integrations reliable

Reliable webhooks turn real-time data from an incident channel into an asset.

Our engineers design and operate event-driven integrations — idempotent webhook consumers, dead-letter queues, signature verification, and reconciliation jobs — so real-time data stays reliable instead of becoming an incident channel.

Free consultationExpert guidanceTailored solutions
What we build

Webhook & integration engagements

  • →Idempotent webhook consumers with dedup stores
  • →Accept-then-queue ingestion architectures
  • →Dead-letter queues with replay and alerting
  • →Signature verification & replay protection
  • →Webhook-plus-polling reconciliation jobs
FAQ · Webhook reliability reference

The questions we get every week.

No. SQS Standard uses that term for its queue contract, but an upstream provider can exhaust a finite delivery budget. Keep duplicate handling and gap recovery as separate concerns.
Digital Applied newsletter

Deep dives on AI, marketing and development.

Practical guides and fresh insights by email. No recycled takes.