Workflow AutomationSecurityn8nZapierMake

How to verify webhook signatures in n8n, Zapier, Make

None of Zapier, Make, or n8n verifies webhook signatures by default, so a public webhook URL is an unauthenticated endpoint anyone can POST forged data to. The fix is to verify the provider's HMAC signature (Stripe's Stripe-Signature, GitHub's X-Hub-Signature-256, Shopify's X-Shopify-Hmac-Sha256) in a code or crypto step before your flow acts, computing the hash over the raw request body.

Alexey YushkinFounder, GENERAL INFORMATICS2 min read

Your automation's webhook trigger is a public URL that runs your flow with whatever data is POSTed to it, and none of Zapier, Make, or n8n checks who sent that data by default. Anyone who learns the URL can forge a request: a fake payment success, a fake lead, a fake order. The fix is signature verification. Real providers sign each webhook with a shared secret using HMAC, and you recompute that signature on your side and reject anything that does not match, before your flow does anything irreversible.

This is one of the most common holes I find in otherwise careful automations. The flow has retries, logging, error handling, and a dedupe gate, and then the front door is a URL that trusts every knock. Closing it is cheap. It is one step, placed first.

A webhook URL is a public endpoint, not a private one

The trigger URL looks like a secret. It is long, random, and you only ever paste it into the sender's settings, so it feels private. It is not. That URL is a remote control for your business logic, and it leaks in ordinary ways: browser history, a screenshot in a support ticket, a Slack thread, a contractor's exported workflow JSON, an error log that captured the full request. The day it leaks, anyone holding it can trigger your flow with fields they chose.

Think about what that means per flow. A forged invoice.paid event marks an order as paid and ships product. A forged lead drops a fake record straight into the CRM and pollutes the pipeline that feeds your customer acquisition systems. A forged "deal won" fires a commission payout. None of these require breaking in. They require knowing a URL that your automation treats as proof of a real event.

A bare webhook URL is a password at best, and a bad one: you cannot rotate it without rewiring every sender, and it proves nothing about the data it carries. Signature verification fixes both problems. It checks that the request came from the real sender and that the body was not changed on the way in.

What none of the no-code tools verify for you

Here is the uncomfortable part. The three platforms most small operators run on do not verify provider signatures out of the box.

PlatformWebhook triggerSignature verification built in?
ZapierWebhooks by Zapier (Catch Hook)No. Accepts any POST to the URL.
MakeCustom webhookNo. No native signature step.
n8nWebhook nodeNo HMAC verification. Header Auth and Basic Auth exist, but those are static tokens.

n8n's Header Auth deserves a clarification, because people reach for it and assume they are covered. It checks that the request carries a fixed header value you set, like a shared password. That is genuinely better than nothing, and for an internal sender it may be enough. But it is one static secret pasted into the sender. If it leaks, you are back to the same problem, and because the secret is not tied to the body, it cannot tell you the payload was tampered with. A real signature binds the secret to the exact bytes of the request, so it does two jobs at once: authenticate the sender, and detect any change to the content.

How a signature works, and the per-provider reference

The mechanism is simple. The sender takes the raw request body, sometimes with a timestamp, runs it through HMAC with a secret that only the two of you know, and puts the resulting hash in a header. You recompute the same HMAC with the same secret on your side. If your hash equals the one in the header, the request is authentic and unmodified. The secret is never sent, so capturing the request does not reveal it.

The details differ by provider, and getting one detail wrong is most of the pain. Here are the three you will meet most often, verified against current vendor docs as of June 2026.

ProviderHeaderWhat is signedAlgorithmEncodingSecret
StripeStripe-Signature (t= and v1=)timestamp + . + raw bodyHMAC-SHA256hexendpoint signing secret (whsec_...)
GitHubX-Hub-Signature-256 (with sha256= prefix)raw bodyHMAC-SHA256hexthe webhook secret you set
ShopifyX-Shopify-Hmac-Sha256raw bodyHMAC-SHA256base64the app's client secret

All three use HMAC-SHA256, which is the good news: one algorithm covers the common cases. The traps are in the framing. Stripe folds a timestamp into the signed string and packs the result into a structured header, so you parse out t= and v1= and rebuild timestamp.body before you hash. GitHub prefixes its hex digest with sha256=, so you compare against the part after the equals sign. Shopify hands you base64, not hex, so your computed digest has to be base64 too or it will never match. Read the provider's signing doc once and copy the scheme exactly.

The one pitfall that breaks every verification: the raw body

If you take one thing from this article, take this. The HMAC is computed over the exact bytes the provider sent. The instant your platform parses the JSON and re-serializes it, key order, whitespace, and number formatting can shift, and your hash will never match even when the secret is right. The verification fails, you assume the secret is wrong, and you lose an afternoon. Verify against the raw, unparsed body. Always.

Each platform has a specific way to get at it.

  • n8n. Turn on the Raw Body option in the Webhook node, then read the unparsed payload as {{ $json.rawBody }}. Feed that into a Crypto node set to the HMAC operation, SHA256, with your signing secret, and compare its output to the value from the signature header in an IF node. If you prefer code, a Code node does the same in one line: crypto.createHmac('sha256', secret).update(raw).digest('hex'). There is an open feature request to make this a native toggle, but today you build it yourself.
  • Zapier. Use Catch Raw Hook, not the standard Catch Hook, so you receive the body before Zapier parses it. Then add a Code by Zapier step to HMAC the raw body with your secret and compare it to the header. If you start from the parsed Catch Hook trigger, you cannot reliably reconstruct what the provider signed.
  • Make. Make parses incoming webhook bodies, which makes raw-body verification awkward. Make's hash functions can produce an HMAC, so a plain hash-over-body scheme is doable by hand, but for anything with a timestamp or a structured header it is fiddly enough that routing the webhook through one small verification step first, and only forwarding requests that pass, is usually cleaner than fighting the parser.

One more refinement for the code paths: compare the two hashes with a timing-safe equality check where your tool exposes one, such as crypto.timingSafeEqual in a code step. A naive string comparison leaks tiny timing differences that a determined attacker can measure. For most small operators this is a minor risk next to having no check at all, but use the constant-time path when the code step makes it free.

Signatures stop forgery, not replay

A signature proves the body is authentic and unchanged. It does not prove the request is fresh. Someone who captures one valid signed request can send it again, and the signature still checks out, because it is a real request. So a signed webhook still needs two more guards.

The first is a freshness check. Stripe builds the timestamp into the signed payload for exactly this reason, and its libraries reject any request whose timestamp is more than five minutes old by default. If you verify manually, compare the t= value against the current time and drop anything stale. Do not set the tolerance to zero, which disables the check entirely and breaks on normal clock drift.

The second is deduplication. Record the provider's event id and refuse to process the same one twice. That is the same dedupe gate covered in why automations silently break, and here it doubles as your replay defense. The clean order is verify the signature, then check freshness, then dedupe on the event id, then act. Each step is cheap, and together they turn an open endpoint into one that only runs on genuine, recent, not-yet-seen events.

What to do next

Pick your highest-stakes webhook, the one wired to money, payments, or your CRM. Confirm the sender signs its requests. Stripe, GitHub, Shopify, and most billing and commerce platforms do, and their docs will name the signing secret and the header. Then add the verify step as the first node after the trigger: recompute the HMAC over the raw body, compare it to the header, and stop on mismatch.

If the sender does not sign at all, fall back to a long, hard-to-guess URL plus a shared secret in a required header, and treat that as a weaker control you will want to revisit. We build workflow automation systems with the verify-then-dedupe-then-act order in from the start. If you have a public webhook running today with no check on the door, send us the flow and we will tell you exactly what can be forged.

Frequently Asked Questions

SOURCES & CITATIONS

  1. Receive Stripe events in your webhook endpoint Stripehttps://docs.stripe.com/webhooks
  2. Validating webhook deliveries GitHubhttps://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries
  3. Verify webhook deliveries Shopifyhttps://shopify.dev/docs/apps/build/webhooks/verify-deliveries
  4. Webhook node documentation n8n Docshttps://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.webhook/

About Alexey Yushkin

Alexey is the founder of GENERAL INFORMATICS LLC. He designs and ships AI and automation systems for businesses and operators across the US.

Connect on LinkedIn

Related reading

Want this kind of system in your business?

We build practical AI and automation systems for operators. Send us your current workflow and we will show you what to automate first.

Request a Workflow Review