Webhook Examples: Build a Receiver That Survives Retries and Crashes

Playcode Team
18 min read
#webhooks #integration examples #reliability #Playcode

The useful webhook example is not the controller that parses JSON and prints an event. It is the boundary that authenticates the exact delivery, stores one durable identity and its pending work together, acknowledges according to the provider contract, and can recover after a retry, outage, deploy, or worker crash without repeating the business effect.

This guide includes a dependency-free Node.js receiver and ten deterministic fixtures. The artifact demonstrates ordering and restart behavior with fictional data. It does not claim one universal signature format, response code, retry schedule, provider delivery guarantee, or live Playcode deployment.

Illustrative webhook operations console showing signature verification, atomic event and outbox storage, retries, and quarantine
Illustrative receiver operations console, not a product screenshot or live provider dashboard. The records are fictional, and the actual result depends on your provider contract, data model, and brief.

QUICK ANSWER

What is a secure webhook receiver example?

A secure receiver preserves the exact raw request bytes, verifies the provider-defined signature before JSON parsing, validates the event identity, and writes that identity plus pending work in one durable transaction. Exact duplicates return the provider-approved acknowledgement without repeating work. A leased worker retries failures, while changed-content replays are quarantined and ambiguous external effects are reconciled.

Write the provider contract and durability boundary first

A callback URL is only one part of the system. Before implementation, write down the provider-specific delivery contract and the business effect that must remain correct after repetition.

  • Current provider documentation: GitHub explains its exact raw-body signature contract, while Stripe documents a different signed-payload and timestamp contract. Use these as evidence that provider contracts differ, not as interchangeable code.
  • A public HTTPS receiver that exposes raw bytes: Your framework must let server code read the exact bytes required by the signature scheme before any JSON middleware, body normalization, character conversion, or field coercion changes them. Bound the request size before buffering it.
  • A transactional durable store: Use a database capable of inserting one provider delivery identity under a unique constraint and inserting its pending outbox work in the same transaction. Configure real durability, backups, restore, and isolation separately.
  • A worker with a lease and retry budget: The worker must claim pending work, expose lease expiry, use bounded backoff, distinguish terminal from retryable failures, and leave an inspectable state when the dependency remains unavailable.
  • A downstream idempotency or reconciliation decision: An outbox makes work durable, but it cannot make an external effect exactly once. Use a provider-supported idempotency key where available, store the returned effect identity, and define how an operator reconciles an ambiguous timeout.

Credentials and access

CredentialMinimum accessStorage and rotation
Webhook signing secret or public verification key
The exact server-side credential defined by the selected provider
Only the receiver verification boundary and the reviewed rotation operation need access. Business workers should receive an authenticated internal event, not the signing credential.Store it as a server-side Playcode secret. Never place it in browser code, the callback URL, generated UI, source control, fixtures, support tickets, or logs. Follow the provider overlap or dual-key procedure when one exists, identify key versions without logging key material, deploy verification support first, rotate the provider configuration, then remove the old key after a controlled smoke.
Downstream API credential
A scoped server-side token only when the worker calls another provider
Grant only the operation the worker performs. A signing secret that authenticates inbound delivery must not double as an outbound API credential.Store separately from the signing secret, restrict it to the worker, and redact request headers, response bodies, and token-bearing URLs. Replace it through the downstream provider procedure, stop unsafe retries during the cutover, verify the new credential with a non-destructive request, and revoke the old value.

Choose where acknowledgement ends and business work begins

The central decision is not which framework receives HTTP. It is which state must be durable before the provider sees an acknowledgement and which work is allowed to continue asynchronously.

ApproachBest forTradeoff
Parse and finish work inside the requestA tiny non-critical internal callback where the provider deadline, retry behavior, and downstream latency are all tightly controlled.A slow dependency can miss the acknowledgement deadline, while a crash after the business write but before the response can trigger redelivery and a repeated effect.
Store the event, then run work inside the requestA bounded transition where the business write itself can share one transaction with the delivery identity and no external effect is required.It removes one deduplication gap, but long or external work still couples provider delivery to dependency latency and failure behavior.
Store the event and outbox together, then dispatchProduction callbacks that trigger email, payment, access, sync, fulfillment, or another dependency that can fail independently.You must operate a lease, retry, dead-letter or quarantine, monitoring, and reconciliation path. External effects still need their own idempotency contract.

Recommended:Use one database transaction for the provider delivery identity and pending outbox work, then acknowledge with the exact response the current provider expects. Let a worker lease and retry the outbox. This is more operational work, but it makes the crash boundaries visible instead of depending on a provider retry after a successful acknowledgement.

Build and verify a durable webhook receiver

Define the provider contract, authenticate exact raw bytes, claim event identity and pending work atomically, dispatch with leases and idempotency, and verify crash and recovery behavior.

STEP 01

Freeze the delivery and response contract

Record the provider rules before choosing status codes or signature code.

GitHub explains its exact raw-body signature contract, while Stripe documents a different signed-payload and timestamp contract. Use these as evidence that provider contracts differ, not as interchangeable code.

Record the exact delivery identity, signed bytes, signature headers, algorithm, timestamp tolerance, body limit, acknowledgement deadline, retry schedule, redelivery behavior, event retention, endpoint registration, secret rotation, and accepted response classes. A generic 204, 202, or 409 is not a provider-neutral rule.

Expected result: The brief names one current provider contract and has no placeholder response, retry, or signing assumptions.

Verify it: Have a second reviewer trace every header, byte sequence, response class, and time window to the current first-party documentation and record the review date.

STEP 02

Define event, outbox, and quarantine records

Make identity and lifecycle explicit before writing the handler.

The event record needs the provider delivery or event ID, a safe raw-body fingerprint, allowed event type, provider occurrence time, receive time, processing status, completion time, and bounded error context. Retain the raw payload only when it is necessary, authorized, and governed by a deletion policy.

The outbox needs a stable ID, event relationship, pending or processing status, attempt count, next attempt, lease token, lease expiry, downstream idempotency key, provider effect ID, and bounded error code. Quarantine changed content under an existing identity without overwriting the accepted fingerprint.

Expected result: A schema review can point to the unique identity, atomic foreign relationship, retry state, lease, effect identity, retention owner, and operator repair path.

Verify it: Write down the state after a new delivery, exact duplicate, conflicting duplicate, retryable outage, terminal rejection, worker crash, and successful completion. No state may be described only as “handled.”

STEP 03

Verify the exact raw body before parsing

Authenticate transport before trusting any JSON field or delivery identity.

Read and bound the raw bytes without body-parser mutation. Extract only the provider-defined headers, calculate or invoke the official verifier over the exact canonical input, use a timing-safe comparison where the provider guidance requires it, and apply the provider timestamp policy before deduplication.

The example signs `timestamp.deliveryId.rawBody` with HMAC-SHA256 only to make the test executable. Do not copy that message format into a GitHub, Stripe, Slack, Meta, or other receiver.

provider-receiver.mjs
const rawBody = await readBoundedRawBody(request)
const verified = verifyCurrentProviderSignature({
  rawBody,
  headers: request.headers,
  secret: env.WEBHOOK_SIGNING_SECRET,
})

if (!verified.ok) return providerInvalidSignatureResponse()
if (!currentProviderTimestampPolicyAllows(verified.timestamp)) {
  return providerStaleSignatureResponse()
}

const event = parseAndValidateAllowedEvent(rawBody)

Expected result: A wrong signature paired with malformed JSON stops at authentication; authenticated malformed JSON reaches the separate payload error.

Verify it: Run both artifact cases and confirm the invalid-signature path creates no event, outbox, quarantine, or business record.

STEP 04

Commit delivery identity and pending work atomically

Remove the crash gap before returning the provider acknowledgement.

Inside one transaction, insert the provider delivery identity under a unique constraint, store a safe fingerprint, and insert the pending outbox row. An exact duplicate returns the prior receipt. Changed bytes under the same identity enter quarantine rather than replacing the first accepted event.

Only acknowledge after the transaction commits. If the process crashes before commit, the provider can redeliver a new event. If it crashes after commit but before acknowledgement, redelivery finds the durable identity and does not add a second work item.

receive-webhook.ts
const receipt = await database.transaction(async (tx) => {
  const existing = await tx.webhookEvent.findByProviderId(event.id)
  if (existing) return compareExistingReceipt(existing, sha256(rawBody))

  const stored = await tx.webhookEvent.insert({
    providerId: event.id,
    payloadHash: sha256(rawBody),
    status: 'pending',
  })
  await tx.outbox.insert({
    id: `outbox:${stored.id}`,
    eventId: stored.id,
    status: 'pending',
  })
  return { kind: 'accepted', stored }
})

return responseForCurrentProvider(receipt.kind)

Expected result: Every accepted event has exactly one pending outbox row at the same commit boundary, and a duplicate cannot create another row.

Verify it: Run the before-commit and after-commit-before-ack crash fixtures, reopen the store, and inspect exact event and outbox counts after redelivery.

STEP 05

Lease work and make the external effect retry-safe

Separate durable acceptance from dependency success.

Claim a pending or expired row with a unique lease token and expiry. On a retryable failure, clear the lease, store a bounded error code, and schedule bounded backoff. On success, store the downstream effect ID and complete the event only if the worker still owns the lease.

Pass the stable outbox ID as the downstream idempotency key when the provider supports it. A worker can crash after the provider accepted the effect but before local completion. The next attempt must return the prior effect or enter reconciliation rather than create a second charge, message, grant, or fulfillment action.

dispatch-webhook-work.ts
const claim = await outbox.leaseNext({ leaseForMs: 30_000 })
if (!claim) return

try {
  const effect = await downstream.apply({
    idempotencyKey: claim.id,
    event: claim.event,
  })
  await outbox.completeIfLeaseOwned(claim, effect.id)
} catch (error) {
  await outbox.retryIfLeaseOwned(claim, boundedErrorCode(error))
}

Expected result: A dependency outage leaves inspectable pending work, and an expired lease can retry with the same downstream identity without duplicating a proven effect.

Verify it: Run the outage and after-effect-before-complete fixtures. Confirm two worker calls produce one downstream effect and one completed outbox row.

STEP 06

Run duplicate, replay, crash, and outage fixtures

Test the failure boundaries directly instead of inferring them from a happy callback.

Download the same-release ZIP, extract it, and run `npm test` inside `durable-webhook-receiver`. Read all ten test names and inspect the state assertions.

The tests use a local atomic file image so state survives a simulated process restart. The README explains why this proves ordering but does not substitute for a production database transaction or a real provider smoke.

Expected result: Ten deterministic tests pass, no network call runs, all data remains fictional, and the source contains no provider credential.

Verify it: Check the process exit code, test count, ZIP file list, ZIP integrity, source byte equality after extraction, credential scan, and the published SHA-256.

STEP 07

Register the exact HTTPS endpoint with the provider

Use the provider console or protected server-side setup path after local proof passes.

Deploy the receiver with the signing credential in server secrets. Verify direct HTTPS without a redirect, confirm the raw-body path in the deployed framework, and register only the intended events. Never place a secret in the callback URL or print a token-bearing setup request.

Trigger the provider verification or test-delivery path using current first-party instructions. Record the provider delivery ID, UTC time, event type, expected response class, receiver status, and any provider request identifier with private payload fields redacted.

Expected result: The provider accepts the callback configuration and a controlled event reaches one durable pending or completed record in the deployed environment.

Verify it: Inspect the provider delivery log and the application state by the same delivery ID. Recheck the provider response and retry documentation instead of assuming the fixture status code applies.

STEP 08

Deploy the receiver and worker as one compatibility change

Keep schema, receiver, worker, and rollback behavior compatible during rollout.

Use expand, deploy, verify, and later contract for schema changes. The old and new worker must both understand pending records during a rolling deployment. Keep acknowledgement behavior stable while the provider may retry old deliveries.

Run the same signed fixture against the published endpoint, then a provider-controlled delivery. Confirm that a worker stop leaves the row pending and that a restart leases it without creating another event.

Expected result: The public receiver returns the provider-approved response, the worker can be stopped and restarted safely, and the rollback does not orphan new-format rows.

Verify it: Capture a redacted release record with commit, schema version, URL, canonical receiver path, test delivery ID, event count, outbox count, response class, worker result, and rollback decision.

STEP 09

Monitor state transitions and rehearse recovery

Observe receipt and processing separately, then practice bounded repair.

Track authenticated, rejected, new, duplicate, quarantined, pending, processing, retrying, completed, and terminal counts; acknowledgement latency; processing latency; oldest pending age; lease expiry; retry attempts; and reconciliation age. Use delivery IDs and internal request IDs, not raw private payloads.

Rehearse secret rotation, worker outage, expired lease, changed-content conflict, provider outage, bad release, database restore, and downstream reconciliation. A whole-app restore can move accepted events backward, so compare the checkpoint with provider and downstream records before replay.

Expected result: An operator can find one event, explain its current state, retry only eligible work, quarantine a conflict, and prove whether an external effect already happened.

Verify it: Use a fictional incident to run the playbook end to end, including who approves replay, how effect identity is checked, how private data is handled, and what closes the incident.

Test authentication, identity, crashes, retries, and the live path

The receiver is only trustworthy when failures at every boundary leave a state that can be explained and recovered without guessing.

TestScenarioExpected result
happy pathA correctly signed, fresh, allowlisted event with a new provider delivery ID reaches the receiver.Signature verification precedes parsing, one event and one pending outbox row commit together, the configured provider acknowledgement returns, and one worker completes one downstream effect.
invalid inputSend wrong-signature malformed JSON, valid-signature malformed JSON, an oversized body, a disallowed event type, and a correctly signed timestamp outside the provider window.Each request fails at its intended boundary without creating an event or business effect. Routine logs contain only a request ID, bounded error code, and safe timing context.
retryDeliver the same signed bytes twice, sign changed bytes under the same delivery ID, crash after commit before acknowledgement, fail the dependency once, and crash after the external effect before local completion.Exact redelivery keeps one event and one work item; changed content is quarantined; durable redelivery returns the prior receipt; the outage retries; and the stable downstream key produces one effect.
production smokeUse the chosen provider to send a controlled event to the deployed HTTPS receiver, stop the worker before processing, then restart it.The provider records an accepted response allowed by its current contract, one durable row waits while the worker is stopped, restart processes it once, and monitoring correlates the full path by delivery identity.

Diagnose webhook failures from transport to effect

Start with the provider delivery record and follow one stable identity through authentication, durable acceptance, leased processing, and downstream reconciliation.

SymptomLikely causeCheckFix
Every real delivery fails signature validation.JSON middleware changed the body, the wrong secret version is loaded, the wrong header or canonical input is used, or the provider and endpoint configurations do not match.Compare credential version identifiers, body byte length, provider delivery ID, signature header presence, and the exact first-party verifier path without printing the secret or raw body.Restore raw-body access, use the current provider verifier and canonical input, align the endpoint configuration and secret version, then repeat one controlled delivery.
The provider shows retries even though events appear in the database.The receiver committed after the provider deadline, returned a response class the provider does not accept, crashed before acknowledgement, or a proxy changed the response.Compare provider attempt times with commit and response timing, reverse-proxy logs, and the documented acknowledgement contract for the current event class.Move external work behind the durable outbox, return the exact provider-approved response after commit, remove redirects or proxy rewrites, and verify legitimate redelivery stays single.
A delivery exists but no worker can find pending work.The event and outbox were written in separate transactions, a schema or filter mismatch hides the row, or the receiver acknowledged before pending work became durable.Join event and outbox records by delivery identity, inspect the creating transaction and release schema version, and locate any event with no corresponding work row.Put both inserts in one transaction, backfill only reviewed orphan events, add a parity alert, and stop acknowledgement when the atomic commit fails.
Pending age rises while retries keep failing.The downstream provider is unavailable, credentials are revoked, rate limits are active, the worker cannot acquire a lease, or a non-retryable validation error is misclassified.Inspect oldest pending age, attempt count, next retry, lease ownership, bounded error class, rate-limit headers, credential version, and provider status without dumping private payloads.Repair the dependency or credential, honor provider backoff, classify terminal errors into review, and resume bounded work while watching duplicate and effect counts.
The same customer action happened twice.The worker used a new downstream key on retry, the provider ignores the key, a timeout was retried without reconciliation, or a completed lease update was lost after the external effect.Trace the delivery ID, outbox ID, every lease token, downstream idempotency key, provider effect IDs, and business record versions to find the first divergent identity.Stop automatic replay, reconcile the existing effect, use a stable key where supported, add expected-version checks, and require operator review when the provider outcome is ambiguous.
A signed event is quarantined as changed content.The provider reuses an identity with mutable payloads, the application selected the wrong identity field, or a proxy transformed signed content while preserving headers.Compare safe fingerprints, provider documentation, delivery attempt records, and the signed canonical bytes. Do not overwrite the first accepted payload.Hold the event, correct the provider identity model or byte path, decide the provider-specific response, and replay only after an authorized reconciliation proves the intended state.

Operate receipt, work, and external effects as separate states

Deploy

Deploy schema support before receiver code and keep old workers compatible with new pending rows during a rolling release. Store signing and downstream credentials separately, verify direct HTTPS and raw-body access, then register the exact endpoint and allowlisted events with the provider.

The same-release ZIP is locally reproduced evidence only. Before dev or production advances, require its exact public path, the article, the hero asset, the receiver route, and a provider-controlled event to return or behave as expected in that environment.

Monitor

Measure authenticated, rejected, duplicate, quarantined, pending, leased, retrying, completed, and terminal events separately. Alert on oldest pending age, expired leases, repeated signature failures, changed-content conflicts, acknowledgement latency, and reconciliation age.

Correlate with provider delivery ID, internal event ID, outbox ID, and downstream effect ID. Keep signing material, authorization headers, raw private payloads, customer records, and unbounded provider errors out of routine logs.

Recover

For a dependency outage, repair the dependency and resume only eligible pending rows with the same downstream identity. For an ambiguous timeout, reconcile the provider effect before retrying. For secret exposure, rotate through the provider-supported overlap and verify both rejection and legitimate redelivery behavior.

For database or whole-app restore, compare the checkpoint with provider delivery history and downstream effects before replay. Restoring code, files, and database together is different from replaying one event or reconciling one external effect.

Authenticate delivery, then authorize the business action

A valid provider signature proves only the configured delivery path under that provider contract. It does not prove that the event may read or change any tenant, account, order, payment, or private record.

  • Verify the exact signed bytes before parsing, apply the provider timestamp and replay rules before deduplication, and use the provider verifier when one is available.
  • Keep signing secrets, public-key configuration, outbound tokens, and rotation material server-side with separate scopes and owners. Never place secrets in callback URLs or logs.
  • Bound request size, allowed content type, event types, field lengths, nesting, and batch count. Validate every required record identity before the first durable business write.
  • Store provider event or delivery identity under a durable unique constraint. Keep a safe payload fingerprint so changed content cannot hide behind an accepted identity.
  • Apply tenant, actor, resource, state-transition, and expected-version authorization after transport authentication and before the business change.
  • Minimize retained payloads, redact routine logs, define retention and deletion ownership, and treat raw bodies as potentially sensitive even when the signature is valid.
  • Rate-limit resource-intensive validation and operator replay paths without exposing thresholds that materially aid abuse. Never let a public request choose an arbitrary replay identity.
  • Make manual replay a reviewed operation with reason, actor, target identity, expected current state, effect reconciliation, outcome, and audit record.

Webhook receiver questions

Should a webhook always return 200 or 204?

No universal response applies. Providers define accepted status classes, response bodies, acknowledgement deadlines, retry rules, and conflict behavior. Return only after the required durable commit, then use the exact current provider contract.

Why verify the raw body before JSON parsing?

Many providers calculate a signature over exact request bytes. Parsing and serializing JSON can change whitespace, character encoding, key order, or line endings. Authentication must use the canonical input the provider specifies before business logic trusts any field.

What should I use as the webhook idempotency key?

Prefer the stable provider event or delivery identity documented for deduplication. Store it under a unique constraint with a safe payload fingerprint. Do not substitute a mutable business ID unless the provider contract explicitly defines that identity.

Does an outbox guarantee exactly-once processing?

No. It makes pending work durable with the event receipt. A worker can still crash after an external provider accepted an effect but before local completion. Use a stable downstream idempotency key, store the effect ID, and reconcile ambiguous outcomes.

What is the difference between a duplicate and a replay attack?

A legitimate provider redelivery can repeat the same authenticated event because the prior acknowledgement was lost or the provider retried. A malicious or changed replay may fail signature, timestamp, identity, or fingerprint checks. Apply the current provider contract before deduplication and quarantine conflicts.

Should webhook work happen before the HTTP response?

Only the minimum provider-required validation and durable acceptance should normally block acknowledgement. Put slow or failure-prone external work behind a transactional outbox and worker unless one bounded business write can safely share the receipt transaction.

How do I test a webhook without a live provider?

Create deterministic raw-body fixtures with known signing keys, including wrong-signature malformed JSON, valid and invalid identities, exact duplicates, changed-content conflicts, stale signed attempts, crash points, dependency outage, lease expiry, and restart. Then run a separate live provider smoke.

Can Playcode build and host a webhook receiver?

Playcode AI can build the server-side receiver, tests, database-backed state, worker, and operator flow. Playcode Cloud can run backend services, databases, jobs, HTTPS, and custom domains under current plan limits. You still need the provider account, current contract, credentials, registration, policy review, and live smoke.

Build the receiver and run it

Turn the provider contract into a tested webhook service

Describe the event, signature rules, durable records, business effect, and recovery path. Playcode can build the app and run its backend, database, HTTPS endpoint, and worker on Playcode Cloud.

Build My App

Provider accounts, credentials, registration, policies, and live delivery checks remain your responsibility.

Have thoughts on this post?

We'd love to hear from you! Chat with us or send us an email.