Development Choices

Webhook replay protection for no-code automations

Author
Joseph TrasattiMember of technical staff
Published
Section
No-Code
Length
12 min read3 sources cited

Verify each webhook against the provider signature using the untouched body, then reject validly signed deliveries outside a short recency window. Atomically record the provider event or delivery identifier before work begins, and make every downstream action idempotent so authentic retries cannot repeat side effects.

Prerequisites

Before building the automation, confirm that you have:

If the no-code webhook trigger exposes only parsed fields, it cannot perform provider-compatible verification over the original body. Put a small verification endpoint in front of it, or use a hosted component that preserves the raw request. The decision between a visual workflow and custom code is covered in choosing a visual automation or webhook handler. Do not send the request into the automation first and attempt to authenticate it later: by then an untrusted request may already have triggered work.

Verify before parsing

A webhook raw body is signature checked, timestamp checked, deduplicated, and enqueued
Signature, recency, and deduplication solve different replay risks.
  1. Write down the provider’s signed-message contract.

    Record four items before configuring the workflow: the signature header, the signing algorithm, the exact signed message, and the provider identifier used to recognize an event. Treat these as one contract rather than four interchangeable settings.

    Providers do not all sign the same representation. Stripe’s webhook documentation, checked 26 August 2026, says its verification uses the raw body, the Stripe-Signature header, and the endpoint signing secret. Its manually constructed signed message combines the header timestamp, a period, and the actual JSON payload before applying HMAC-SHA256. The same documentation warns that manipulating the raw request body causes verification to fail.

    GitHub’s delivery-validation documentation, checked 26 August 2026, specifies an HMAC-SHA256 digest over the payload contents with the webhook secret, delivered in X-Hub-Signature-256 with a sha256= prefix. It also says to compare signatures with a constant-time operation and to prevent proxies or load balancers from modifying the payload or headers.

    Those examples are not templates to mix. A Stripe verifier should not apply GitHub’s header format, and a GitHub verifier should not construct Stripe’s timestamp-prefixed message. Use the provider’s documented algorithm or maintained verification function for that provider.

    The cost of this step is maintaining a small provider-specific configuration instead of a generic “verify HMAC” block. That is the right cost: a generic block is the wrong answer when it cannot reproduce the provider’s exact signed message, header parsing, encoding, and comparison rules.

  2. Capture the body and headers without transforming them.

    Configure the first executable step to retain the body exactly as received. Read the signature header and, where the provider supplies one, the signed timestamp. Do not parse the JSON first. Do not trim whitespace, rename fields, reorder keys, rebuild the object, convert it to form data, or let an earlier no-code step substitute its own serialized version.

    This ordering matters because the signature authenticates a byte representation, not the business meaning of the decoded fields. Two JSON documents can describe the same object while containing different bytes. Verification against a reconstructed document therefore answers the wrong question.

    Some no-code triggers parse a webhook automatically and expose only friendly fields. Inspect the trigger’s raw-request capability rather than assuming a field named body is raw. Test it with a known provider fixture. If the product cannot expose the original body and signature header before other steps run, the correct arrangement is:

    provider → raw-body verifier → authenticated automation

    The verifier may be a small function, gateway rule, or supported connector component. Its job is limited: preserve the request, authenticate it, enforce recency, claim the event identifier, and then pass a trusted representation forward. This adds one deployed component and another failure point. It is still the suitable choice when the no-code platform cannot supply the primitives required for verification. A custom connector is suitable only if its schema contract also preserves the authentication inputs rather than exposing parsed business fields alone.

  3. Verify the provider signature and reject failures immediately.

    Feed the untouched body, signature header, and endpoint secret into the provider-specific verifier. If implementing the comparison yourself, follow the provider’s documented parsing and use a constant-time comparison. A missing header, malformed signature, unknown supported scheme, or mismatch must stop the run before any parser, router, upload, database write, notification, or external API call executes.

    Keep signing secrets in the platform’s secret store. Do not place them in a visible workflow field, execution log, sample payload, or downstream request. Separate secrets by webhook endpoint where the provider supports that arrangement, so the verifier uses the secret associated with the receiving route.

    Signature verification proves that the signed message matches the secret-based calculation. It does not, by itself, prove that the delivery is new. An attacker who captures an authentic body and its authentic signature does not need to alter either one to replay them. That is why signature verification is the first gate, not the whole replay-control design.

    A verifier that runs after parsing is the wrong answer even if every test payload currently passes. Any later connector update that changes serialization can break verification, and untrusted fields have already entered the workflow. For a focused treatment of the authentication gate, see verifying webhook signatures before an automation acts.

  4. Enforce the signed timestamp’s recency window.

    When the provider includes the timestamp in the signed message, compare that timestamp with the verifier’s synchronized current time and apply the provider’s documented tolerance. Reject a delivery outside the permitted window even when its signature is valid. A valid signature without a time check can still authenticate a captured old delivery.

    Stripe documents this mechanism explicitly: because its timestamp is signed, changing the timestamp invalidates the signature; a correctly signed request with an old timestamp can still be rejected. As checked 26 August 2026, Stripe’s libraries use a default tolerance of five minutes and warn that setting the tolerance to zero disables recency checking. Stripe also recommends synchronizing the server clock with Network Time Protocol.

    Five minutes is a Stripe library default, not a universal webhook setting. For another provider, use that provider’s documented value or choose a window from observed legitimate delivery delay and the workflow’s replay risk. Record the choice in the workflow configuration. A smaller window rejects captured requests sooner but can also reject legitimate deliveries delayed in transit or verification. A larger window accepts more delay but leaves a captured delivery usable for longer.

    Clock synchronization is part of the control. An inaccurate clock can reject current requests or accept requests that should be stale. Monitor clock health on the component that performs verification; synchronizing a later workflow runner does not fix the decision made at the entry point.

    If a provider’s signed contract contains no timestamp, adding the receiver’s arrival time to a database does not turn it into a signed provider timestamp. It can support auditing and retention, but it cannot prove when the provider created the request. In that condition, signature verification plus durable duplicate detection and idempotent downstream work are the available replay controls. Do not claim timestamp freshness that the signed message does not establish.

  5. Parse only the authenticated, recent body.

    After signature and recency checks pass, decode the JSON and validate the fields the workflow will use. Authentication and schema validation answer different questions: the signature establishes that the received bytes match a message signed with the provider secret; schema validation establishes that the decoded data has the shape and values the automation expects.

    Reject an authenticated event if a required identifier is absent, has the wrong type, or cannot be associated with the configured provider. Do not construct a fallback identifier from mutable display names or a subset of business fields. If the provider offers separate event and delivery identifiers, document which one has the duplicate semantics the workflow needs.

    This step costs another explicit failure branch, but it keeps signed yet unusable data away from later actions. It is the wrong place to perform duplicate detection if parsing has already triggered automatic branches. The no-code platform must not evaluate business routes merely because JSON decoding succeeded.

  6. Claim the provider event or delivery identifier atomically.

    Build a deduplication key from the provider identity, endpoint or product-environment scope where relevant, and the provider’s event or delivery identifier. Before starting downstream work, create a durable record with that key using a conditional insert, unique constraint, compare-and-set operation, or equivalent single atomic action.

    The outcomes are binary:

    • If the key is new, mark it as accepted and allow processing to continue.
    • If the key already exists, do not repeat the downstream work. Return the response appropriate for an already accepted delivery and record that a duplicate was suppressed.

    Do not implement this as “search for the ID, then create a row.” Two concurrent runs can both finish the search before either creates the row. The uniqueness decision has to occur at the write itself.

    Stripe says webhook endpoints can receive the same event more than once and recommends logging processed event IDs. It also says that retries receive a new signature and timestamp for each delivery attempt. This distinction is central: a legitimate retry is a new delivery attempt and can pass both signature and recency checks, while still representing an event the automation has already accepted. Store the provider event or delivery identifier; do not use the signature as the deduplication key.

    Retain deduplication records for a period based on the provider’s documented redelivery behavior and the consequence of repeating the action. No retention number is supplied for all providers, so do not copy an arbitrary duration into the control. If the store expires a key while the same event can still be delivered or manually replayed, duplicate suppression ends at expiration.

    A durable store adds reads, writes, retention, and cleanup. An in-memory variable is cheaper but is the wrong answer for multiple workers, restarts, delayed retries, or parallel runs because it does not provide shared durable uniqueness.

  7. Make every downstream action idempotent.

    Deduplication at entry is necessary but not sufficient. The workflow can fail after a downstream action succeeds but before the accepted-event record is marked complete. A retry then sees an incomplete run, and an operator may need to resume it. Each effect therefore needs its own stable idempotency key and state.

    Derive that key from the provider event identifier plus the action’s fixed role, such as event-id:publish-asset or event-id:send-notification. If the downstream API accepts an idempotency key, pass it. If it does not, maintain an action record with states such as pending, completed, and failed, and check the target system before repeating an uncertain operation. The deeper patterns are covered in idempotency keys for no-code API actions.

    Do not use the workflow run ID or delivery signature. Legitimate provider retries create new delivery attempts, and platforms commonly create a new run for each attempt; a run-specific key would authorize the same effect again. The stable unit is the provider event or delivery identity chosen in the previous step, combined with the particular effect.

    Idempotency has a concrete cost: extra state, stable key construction, and reconciliation for calls whose outcome is unknown. It is the wrong answer to label an action idempotent merely because repeating it often appears harmless. Creating a second row, sending another message, or publishing another asset is a second effect unless the target enforces the same key or the workflow proves the desired state already exists.

  8. Return success only after durable acceptance, then test replay paths.

    Return the provider’s success response after signature verification, recency enforcement, schema validation, and the atomic event claim have completed. Long-running effects can continue after durable acceptance if the platform provides a reliable queued handoff. Do not acknowledge first when losing the run between acknowledgement and durable storage would lose the event.

    Exercise the control with five tests:

    1. Change one byte in a captured body while keeping its original signature. Signature verification rejects it before parsing.
    2. Replay an untouched, correctly signed delivery after its signed timestamp falls outside the configured window. Recency enforcement rejects it even though its signature calculation is valid.
    3. Submit the same recent delivery twice. The first run claims the event identifier; the second is suppressed by the atomic claim.
    4. Submit a legitimate provider retry carrying a fresh delivery signature and timestamp for an event already accepted. Authentication and recency pass, but the stored event identifier prevents repeated work.
    5. Interrupt the workflow after one downstream effect succeeds but before the whole run completes. On retry, the effect’s idempotency key or action record prevents a second execution while unfinished effects can continue.

    Record enough status to distinguish signature rejection, stale delivery, duplicate suppression, schema rejection, and downstream failure. These states answer different operational questions and should not collapse into one generic failed run. Use webhook delivery monitoring to connect those states to alerts without exposing bodies, signatures, or secrets in logs.

Expected result

The finished automation has one ordered gate: it preserves the request body, verifies the provider signature, enforces any signed timestamp window with a synchronized clock, parses and validates the authenticated event, atomically claims its provider identifier, and invokes downstream effects with stable idempotency keys. Altered requests, stale captures, concurrent duplicates, and freshly signed legitimate retries do not repeat downstream actions.

Sources

  1. OWASP REST Security Cheat Sheetcheatsheetseries.owasp.org
  2. webhook documentationdocs.stripe.com
  3. delivery-validation documentationdocs.github.com

See also