How to Verify Webhook Signatures Before Calling an AI Model
Stop strangers from running up your AI bill by checking an HMAC signature in n8n or Make before any model call.
A public webhook URL is open to anyone who finds it. If that URL triggers a paid AI model, an attacker can drain your credits with a loop of requests. The fix is a shared secret: the sender signs each payload, and your workflow rejects anything whose signature does not match before spending a token.
What you need
- A webhook in n8n, Make, or Zapier that can read raw body and headers
- A shared secret string you control on both ends
- The ability to add a Code or Tools step before the AI call
Step 1: Sign the payload on the sender
Whatever sends the webhook should compute an HMAC SHA-256 of the exact request body using the shared secret, then put the hex digest in a header such as X-Signature.
SECRET="super-secret-value"
BODY='{"prompt":"hello"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')
curl -X POST https://your-n8n.example.com/webhook/ai \
-H "Content-Type: application/json" \
-H "X-Signature: $SIG" \
-d "$BODY"Step 2: Recompute the signature inside the workflow
In n8n, add a Code node right after the Webhook node. Hash the raw body with the same secret and compare it to the header. Throw an error when they differ so the workflow stops before the model node runs.
const crypto = require('crypto');
const secret = 'super-secret-value';
const raw = JSON.stringify($input.first().json.body);
const sent = $input.first().json.headers['x-signature'];
const calc = crypto.createHmac('sha256', secret).update(raw).digest('hex');
if (!sent || calc !== sent) {
throw new Error('Invalid signature - rejecting request');
}
return $input.all();Step 3: Test both paths
Result: only signed requests reach the model, so a leaked URL alone cannot burn your AI budget.
Watch related tutorials
32:08
21:45
34:10
26:40
32:15
40:20