HomeJournalThis post

Webhook signatures need replay defenses

Replay-aware verification separates authentic bytes, covered context, freshness, atomic nonce claims, domain idempotency, acknowledgement, and reconciliation.

JP
JP Casabianca
Designer/Engineer · Bogotá

A valid signature proves who authorized a message. By itself, it does not prove that the message is new.

An attacker who captures a signed payment, membership, deployment, or hiring event may not need the secret. If the receiver accepts the same authentic bytes outside their intended time and delivery context, replay can repeat or resurrect a consequential action.

RFC 9421 gives HTTP Message Signatures creation, expiration, and nonce parameters, and explicitly lists maximum signature age, expiration checks, and nonce uniqueness as application-level verification requirements. The application still has to define the freshness window, storage scope, atomic claim, clock policy, and effect idempotency.

I would verify authenticity, freshness, and uniqueness as separate decisions. Then I would make the downstream effect idempotent because legitimate webhook systems retry and networks can hide successful responses.

Replay defense is strongest when rejection and reconciliation remain possible without logging secrets or raw sensitive payloads.

01 · AuthenticateVerify the signed message

Read raw bytes once, select allowed key and algorithm, rebuild covered components exactly, verify digest and signature.

02 · FreshnessBound time and uniqueness

Check created and expires against policy, validate destination, atomically claim nonce or delivery ID, and classify duplicates.

03 · ApplyConverge on one outcome

Commit an idempotent domain transition, record receipt, acknowledge only durable acceptance, and reconcile ambiguity.

Figure 1: Verification moves from bytes to one authorized effect.

Verify exact received bytes

The verifier should consume the raw body once and preserve the precise request-target and covered fields before framework parsing or normalization changes the signature input.

I would pressure-test that decision with four questions:

  • Which bytes are signed?
  • Can middleware mutate them?
  • Which target form is covered?
  • Is content digest required?

The failure mode here is re-serializing parsed JSON and assuming equivalent meaning creates equivalent bytes. In webhook receivers and signed HTTP callbacks where an authentic request can be captured, delayed, duplicated, reordered, or delivered again after credentials, subscriptions, or product state have changed, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be a raw-request verification boundary. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is official positive and mutation vectors producing deterministic decisions. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put a raw-request verification boundary beside the question “Which bytes are signed?” before the first implementation review. The next pass would use “Can middleware mutate them?” to test the boundary, then “Which target form is covered?” to expose the state most likely to be missed. I would keep “Is content digest required?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports official positive and mutation vectors producing deterministic decisions.

Constrain key and algorithm policy

Key identity, provider, tenant, algorithm, size, validity interval, and rotation overlap should come from receiver policy rather than untrusted signature hints alone.

The practical review starts here:

  • Who owns this key?
  • Which algorithms are allowed?
  • Was the key valid at creation?
  • How does overlap end?

Those questions keep accepting any resolvable key or advertised algorithm from becoming the default. I would capture the decision in a webhook key and algorithm registry, then use it while the work is still cheap to change. For signed callbacks whose authenticity and freshness are independently reviewable, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like old and new keys working only inside an observable bounded rotation. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make a webhook key and algorithm registry part of the working surface. I would use it to answer “Who owns this key?” while scope is still flexible, and “Which algorithms are allowed?” before code or content becomes expensive to unwind. During QA, “Was the key valid at creation?” and “How does overlap end?” become concrete checks rather than discussion prompts. That sequence turns signed callbacks whose authenticity and freshness are independently reviewable into something the team can operate and gives me a specific outcome to report: old and new keys working only inside an observable bounded rotation.

  1. FreshFirst valid delivery

    Signature, covered context, time window, key state, and replay claim pass; handler may attempt the domain effect.

  2. DuplicateKnown delivery returns safely

    Receiver finds the prior terminal receipt and returns a stable response without rerunning the consequential action.

  3. StaleValidity no longer grants authority

    Expired, future-skewed, revoked-context, reused-nonce, or wrong-destination messages are rejected and observed.

Figure 2: A delivery can be authentic and still unacceptable.

Cover consequential context

Method, authority, target, content digest, content type, and application tag should be covered according to the protocol so authentic bytes cannot be redirected into a different meaning.

Before implementation, I would answer:

  • Which components change meaning?
  • Can a proxy rewrite them?
  • Is destination tenant-bound?
  • Which canonicalization is specified?

The artifact is a covered-component contract. Its job is to expose the tradeoff early enough that design, engineering, support, or product can disagree with something concrete. The common trap is signing only the body while routing context selects the account or action; it moves uncertainty downstream and makes the final interface carry a problem the system never resolved.

For me, the useful receipt is the same envelope rejected when moved to another protected context. That connects a verification envelope that binds exact bytes, destination, method, key identity, creation and expiry time, nonce or delivery identity, replay state, idempotent effect, and reconciliation to an observable result instead of a process claim.

I would test this with one typical case and one boundary case. The typical case should make “Which components change meaning?” easy to answer. The boundary should force a decision about “Can a proxy rewrite them?” and “Is destination tenant-bound?.” I would record both in a covered-component contract, including the part that stayed unresolved after the first pass. The final check, “Which canonicalization is specified?,” is where the artifact earns its place: it either supports the same envelope rejected when moved to another protected context, or it shows exactly why another iteration is needed.

Define the freshness window

Created, expires, receiver time, tolerated clock skew, provider retry horizon, outage recovery, and high-consequence event policy need explicit boundaries.

I would use these prompts during the working review:

  • How old may a signature be?
  • How far future is acceptable?
  • Does expires shorten policy?
  • How are delayed retries recovered?

If the team slips into using a timestamp check without defining clock behavior or delayed legitimate delivery, the product can still look complete while its operating rule stays ambiguous. I would make a freshness-and-skew policy the shared reference and keep it small enough to update as evidence changes.

The standard is stale and future-skew fixtures rejected without breaking documented retries. That tells me whether the decision helped the product, not merely whether the document was completed.

The working sequence is small: draft a freshness-and-skew policy, review it against “How old may a signature be?,” implement the narrowest useful path, and then return with evidence for “How far future is acceptable?.” I would use “Does expires shorten policy?” to inspect product consequence and “How are delayed retries recovered?” to decide whether the result is stable enough to ship. This keeps using a timestamp check without defining clock behavior or delayed legitimate delivery visible as a known risk and makes stale and future-skew fixtures rejected without breaking documented retries the release receipt rather than a hopeful conclusion.

SignalDecisionWorking note
SignatureWere covered bytes authorized?Does not establish freshness, correct tenant, unique processing, or whether the signed component set omitted a consequential field.
NonceWas this envelope claimed before?Needs scoped atomic storage and expiry; it does not make the business action safe to repeat under a new delivery ID.
IdempotencyDid this logical effect already happen?Protects retries and distinct deliveries for one event, but cannot authenticate a forged request.
Figure 3: Security checks answer different questions.

Claim replay identity atomically

Nonce or delivery identity must be scoped to the correct provider and key context, inserted atomically before effect, retained beyond acceptance, and cleaned up after the threat window.

I would pressure-test that decision with four questions:

  • What identifies the envelope?
  • Which scope prevents collision?
  • Is claim atomic across workers?
  • How long is evidence retained?

The failure mode here is checking an in-memory set before several workers process the same delivery. In webhook receivers and signed HTTP callbacks where an authentic request can be captured, delayed, duplicated, reordered, or delivered again after credentials, subscriptions, or product state have changed, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be an atomic replay-claim schema. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is one first claimant under a concurrent duplicate storm. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put an atomic replay-claim schema beside the question “What identifies the envelope?” before the first implementation review. The next pass would use “Which scope prevents collision?” to test the boundary, then “Is claim atomic across workers?” to expose the state most likely to be missed. I would keep “How long is evidence retained?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports one first claimant under a concurrent duplicate storm.

Keep the domain effect idempotent

A unique delivery is not always a unique business event, so event version, aggregate identity, transition rules, and downstream keys should converge on one valid outcome.

The practical review starts here:

  • What is the logical event?
  • Can two deliveries represent it?
  • Is the transition still valid?
  • Do downstream calls share the key?

Those questions keep treating nonce uniqueness as permission to repeat a payment or state transition from becoming the default. I would capture the decision in a domain idempotency contract, then use it while the work is still cheap to change. For signed callbacks whose authenticity and freshness are independently reviewable, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like distinct legitimate deliveries resolving to one intended product effect. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make a domain idempotency contract part of the working surface. I would use it to answer “What is the logical event?” while scope is still flexible, and “Can two deliveries represent it?” before code or content becomes expensive to unwind. During QA, “Is the transition still valid?” and “Do downstream calls share the key?” become concrete checks rather than discussion prompts. That sequence turns signed callbacks whose authenticity and freshness are independently reviewable into something the team can operate and gives me a specific outcome to report: distinct legitimate deliveries resolving to one intended product effect.

Handle acknowledgement ambiguity

The receiver should acknowledge only after durable acceptance and return stable responses for known duplicates while timeouts and crashes remain safe to retry.

Before implementation, I would answer:

  • When is 2xx emitted?
  • What if commit succeeds before response?
  • How is duplicate success represented?
  • Which failures request retry?

The artifact is a response-and-commit state table. Its job is to expose the tradeoff early enough that design, engineering, support, or product can disagree with something concrete. The common trap is returning success before persistence or generic failure after known success; it moves uncertainty downstream and makes the final interface carry a problem the system never resolved.

For me, the useful receipt is provider retry converging without duplicate effect or endless redelivery. That connects a verification envelope that binds exact bytes, destination, method, key identity, creation and expiry time, nonce or delivery identity, replay state, idempotent effect, and reconciliation to an observable result instead of a process claim.

I would test this with one typical case and one boundary case. The typical case should make “When is 2xx emitted?” easy to answer. The boundary should force a decision about “What if commit succeeds before response?” and “How is duplicate success represented?.” I would record both in a response-and-commit state table, including the part that stayed unresolved after the first pass. The final check, “Which failures request retry?,” is where the artifact earns its place: it either supports provider retry converging without duplicate effect or endless redelivery, or it shows exactly why another iteration is needed.

Log decisions without secrets

Receipts should retain hashes, IDs, times, key labels, policy versions, reason codes, and effect references while excluding shared secrets, signature bases containing sensitive fields, and unnecessary payloads.

I would use these prompts during the working review:

  • Which evidence supports investigation?
  • Can logs replay the request?
  • Are payload fields sensitive?
  • Who can read rejection details?

If the team slips into logging the complete request and signature material during a security failure, the product can still look complete while its operating rule stays ambiguous. I would make a redacted verification event schema the shared reference and keep it small enough to update as evidence changes.

The standard is operators explaining acceptance or rejection without expanding exposure. That tells me whether the decision helped the product, not merely whether the document was completed.

The working sequence is small: draft a redacted verification event schema, review it against “Which evidence supports investigation?,” implement the narrowest useful path, and then return with evidence for “Can logs replay the request?.” I would use “Are payload fields sensitive?” to inspect product consequence and “Who can read rejection details?” to decide whether the result is stable enough to ship. This keeps logging the complete request and signature material during a security failure visible as a known risk and makes operators explaining acceptance or rejection without expanding exposure the release receipt rather than a hopeful conclusion.

Reconcile delivery and product state

A signed-event ledger, provider event query, domain transition, and downstream outcomes should be comparable when acknowledgements, ordering, or handler versions create uncertainty.

I would pressure-test that decision with four questions:

  • Which events are missing?
  • Which effect lacks delivery?
  • Can order change meaning?
  • How is repair authorized?

The failure mode here is assuming signature verification makes delivery complete. In webhook receivers and signed HTTP callbacks where an authentic request can be captured, delayed, duplicated, reordered, or delivered again after credentials, subscriptions, or product state have changed, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be a webhook reconciliation view. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is gaps and duplicate attempts discoverable before customers report them. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put a webhook reconciliation view beside the question “Which events are missing?” before the first implementation review. The next pass would use “Which effect lacks delivery?” to test the boundary, then “Can order change meaning?” to expose the state most likely to be missed. I would keep “How is repair authorized?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports gaps and duplicate attempts discoverable before customers report them.

Test replay as a protocol

QA should mutate covered fields, reuse nonce concurrently, skew time, rotate keys, replay after expiry, reorder versions, crash around commit, and redeliver through another region.

The practical review starts here:

  • Does one byte fail verification?
  • Can two workers both claim?
  • What happens across rotation?
  • Does crash recovery converge?

Those questions keep testing one valid signature and one wrong secret from becoming the default. I would capture the decision in a webhook adversarial vector suite, then use it while the work is still cheap to change. For signed callbacks whose authenticity and freshness are independently reviewable, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like authenticity, freshness, uniqueness, and idempotency failing independently and safely. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make a webhook adversarial vector suite part of the working surface. I would use it to answer “Does one byte fail verification?” while scope is still flexible, and “Can two workers both claim?” before code or content becomes expensive to unwind. During QA, “What happens across rotation?” and “Does crash recovery converge?” become concrete checks rather than discussion prompts. That sequence turns signed callbacks whose authenticity and freshness are independently reviewable into something the team can operate and gives me a specific outcome to report: authenticity, freshness, uniqueness, and idempotency failing independently and safely.

What I would show in the work

The public version needs evidence from the work itself. For this topic, the first five artifacts I would reach for are:

  • a raw-request verification boundary
  • a webhook key and algorithm registry
  • a covered-component contract
  • a freshness-and-skew policy
  • an atomic replay-claim schema

I would not publish all five at equal weight. One should orient the reader, one should reveal the hardest tradeoff, and one should prove the result. The others can live in a downloadable note or appear as supporting frames. That edit matters because a verification envelope that binds exact bytes, destination, method, key identity, creation and expiry time, nonce or delivery identity, replay state, idempotent effect, and reconciliation becomes harder to understand when every process detail is treated as equally important.

I would also show one rejected direction. The useful version is specific: which option looked attractive, which constraint made it wrong, and what evidence supported the narrower choice. That gives an engineering manager something real to question and keeps the case study from reading like the final answer was obvious from the beginning.

webhook-verification-receipt.json
# envelope
provider p_7 / key k_3 / created 20:01Z
POST /hooks/payments, digest sha-256:6a…, signature label sig1, age 8s, maximum 300s, nonce hash n:91….

# decision signature pass / nonce claimed Allowed algorithm, exact target covered, key active at creation, clock skew +2s, delivery d_847 first seen.

# effect invoice inv_42 transitioned once Domain idempotency payment:evt_903, transaction 2817, response 204, duplicate count 2, reconciliation link retained.

Figure 4: A verification receipt avoids storing the secret payload.

Resource path

The practical follow-up I would build is a webhook verification pack with canonical input, covered components, digest, key lookup, rotation window, maximum age, clock tolerance, nonce storage, delivery identity, atomic replay claim, idempotency boundary, response policy, redacted logs, retry fixtures, reconciliation, and incident steps. I am treating that as a resource backlog item, not pretending the adjacent downloads below are the same artifact. The related cards cover useful pieces of the workflow today; this specific file should only be published when its examples, fields, and instructions are complete.

The first version should stay concise: context, constraint, decision, evidence, owner, and follow-up. Its value would come from helping someone repeat this exact review, not from adding another generic PDF to the site.

Review checklist

The article-specific review questions are:

  • Which bytes are signed?
  • Who owns this key?
  • Which components change meaning?
  • How old may a signature be?
  • What identifies the envelope?
  • What is the logical event?
  • When is 2xx emitted?
  • Which evidence supports investigation?
  • Which events are missing?
  • Does one byte fail verification?

I would add two editorial checks before publishing: can a recruiter find the point in the first minute, and can an engineer trace at least one claim to an implementation or production receipt? If either answer is no, the article needs another edit.

Implementation notes

For signed callbacks whose authenticity and freshness are independently reviewable, I would write the implementation note before polish. It would name the changed surface, source of truth, owner, failure boundary, and verification path. Those details prevent the principle from floating above the actual code or operational workflow.

The proof signals I care about are specific to this article:

  • distinct legitimate deliveries resolving to one intended product effect
  • provider retry converging without duplicate effect or endless redelivery
  • operators explaining acceptance or rejection without expanding exposure
  • gaps and duplicate attempts discoverable before customers report them
  • authenticity, freshness, uniqueness, and idempotency failing independently and safely

I would choose two or three of those signals for the first release rather than instrumenting everything. The strongest pair usually combines one direct behavior check with one operating check: a route and a data query, a keyboard path and a support state, a handler replay and a reconciliation result, or a migration count and a rendered screen.

The follow-up belongs in the note before shipping. It should say what remains temporary, what evidence would trigger another pass, and who owns that decision. That is how the first version stays intentionally narrow without making the boundary invisible.

Case-study packaging

I would structure the case-study version around the four visual lessons already established:

  • Verification moves from bytes to one authorized effect.
  • A delivery can be authentic and still unacceptable.
  • Security checks answer different questions.
  • A verification receipt avoids storing the secret payload.

The opening frame explains the product pressure. The middle two show the decision moving through the system. The last frame is the receipt: what was checked, what held, and what remained unresolved. That order lets the reader move from product judgment into implementation detail without reconstructing the whole project first.

I would include one caveat tied to webhook receivers and signed HTTP callbacks where an authentic request can be captured, delayed, duplicated, reordered, or delivered again after credentials, subscriptions, or product state have changed: a data limit, rollout boundary, unsupported state, external dependency, or result that is still directional. A precise caveat makes the evidence easier to trust because it shows where the claim stops.

The final test is whether the page creates a better conversation. If the artifact helps someone ask a sharper question about product judgment, implementation detail, or release proof in a live interview, it belongs in the story.

Interview angle

In an interview, I would explain this through a verification envelope that binds exact bytes, destination, method, key identity, creation and expiry time, nonce or delivery identity, replay state, idempotent effect, and reconciliation. The story should start with the product pressure, then move into the system constraint, the artifact, and the proof. That order keeps the answer grounded. It also gives the interviewer several places to go deeper: data, frontend architecture, design systems, support, migration, accessibility, or release process.

The strongest version of the answer includes a tradeoff. I want to be able to say what I chose, what I left alone, and how I knew the work helped. That is more credible than presenting every project as a clean win.

The hiring signal

A replay-aware webhook verifier is a hiring signal because it shows I can connect cryptographic verification, HTTP semantics, distributed delivery, transactional state, incident response, and user-visible reconciliation.

That is the level I want this site to communicate. The work should show taste, but it should also show operating judgment. It should make me look like someone who can enter a real product system, understand the messy middle, ship the useful version, and leave enough proof for the next person to trust it.

Companion artifacts

Use this after reading.

Practical downloads and templates that turn the article into something you can bring into a product review, implementation pass, or agent workflow.

RepoJun 2026

Agent-Ready API Spec Template

An OpenAPI and Postman starter template for APIs that AI agents can discover, call, and recover from safely.

OpenAPIPostmanAI agents
View details
TemplateJul 2026

Dependency Adoption Receipt

A reviewable receipt for package need, identity, provenance, permissions, supply-chain risk, verification, ownership, and removal.

Supply chainSecurityAI-assisted
View details
DownloadJun 2026

UI PR Risk Review Checklist

A merge-readiness checklist for product intent, states, accessibility, visual durability, and UI implementation risk.

UI reviewQAFrontend
View details