Development Choices

Verifying Webhook Signatures Before a Media Flow Acts

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

Verify a webhook's signature and timestamp before any step of a media automation reads the payload. The endpoint is a public URL, the signature is derived from the payload and a shared secret, and a valid signature on an old timestamp is a replay. Reject on either failure, then let the flow branch.

What you need before starting

Why this step exists at all

A webhook endpoint is a public URL. Anything that acts on an unverified payload acts on whatever the internet sends it — a scanner, a former contractor with the URL in their shell history, a script that guessed it. Cloudinary’s notifications documentation describes the payload a flow receives; nothing about the payload’s shape tells you who sent it. Only the signature does.

This is also the one part of a no-code automation where getting it wrong has a security consequence rather than an operational one. A mistyped transformation costs credits. A moderation flow that deletes or publishes on a forged “approved” event has been driven by a stranger. Everything else in the flow can be fixed after the fact; this cannot.

Steps

  1. Find the signature and timestamp on an incoming notification. Each notification carries a signature derived from the payload and the shared secret, plus the timestamp it was signed at. The signature verification section of the notifications docs names the headers and shows how the value is computed. Log both from your captured sample so you know exactly what the flow will read.

  2. Make verification the first thing that runs, before any branch reads the payload. In a block-based builder this means the trigger connects to a verify step and to nothing else; every condition, lookup and action hangs off the verify step’s success output. The order is not cosmetic. A forged payload is designed to be read: its public_id points at the asset the attacker wants touched, its moderation status says what they want it to say. A branch that inspects the body first and verifies later has already let the payload steer. If your builder cannot put a step ahead of the branches, that is one of the signals a flow should become code.

  3. Recompute the signature and compare. Follow the documented computation over the raw body, the timestamp and the secret, and compare the result to the header value. Two details decide whether this actually holds:

    • Compute over the raw bytes as received. A JSON-parsing block that re-serialises the body — reordering keys, dropping whitespace — changes the input and the signature will never match, even for genuine notifications.
    • Use a constant-time comparison if the builder offers one, or the SDK helper Cloudinary documents, which does this for you. A plain string equality is the lazy version and is usually acceptable for a webhook, but the helper costs nothing extra.
  4. Check the timestamp as well as the signature. Signatures are time-bound: a payload that was genuine an hour ago is still signed correctly now, so a captured notification replayed later passes step 3. Reject anything whose timestamp is outside your accepted window. The Cloudinary SDK verifiers take a validity period for exactly this reason; the docs give the default. Choose the window to cover realistic delivery delay and clock skew, not convenience — a day-long window has quietly turned replay protection off.

  5. Route failures to a dead end, not to a branch. A failed check should return a 4xx and stop. Do not log the payload contents into a place another flow reads, and do not “soft-fail” into the normal path with a flag — a downstream block will forget to check the flag. If you run a scheduled reconciliation flow, that is where a legitimately dropped event gets picked up; the webhook handler is not the place to be forgiving.

  6. Prove it against your captured sample, then against a tampered copy. Replay the raw notification: it must pass. Change one character in the body and replay: it must fail on the signature. Replay the untouched original after the window has elapsed: it must fail on the timestamp. The MediaFlows build-flow documentation covers testing a flow with a sample event, which is the mechanism to use for all three runs.

Expected result

The trigger feeds a single verify step. That step reads the signature and timestamp headers, recomputes the signature over the raw body with the product environment’s secret, rejects on mismatch, rejects on a timestamp outside the window, and only then hands the payload to the branches. A genuine notification flows through unchanged; a modified body and a stale replay both stop at the first block with a 4xx and no downstream action. Nothing after that block ever sees an unverified payload.

Sources

  1. notifications documentationcloudinary.com
  2. signature verification section of the notifications docscloudinary.com
  3. MediaFlows build-flow documentationcloudinary.com

See also