Sketch: payment webhook in n8n with signature check + dedupe. What am I missing?
I'm sketching a handler for payment-provider webhooks in n8n (for a subscription flow) and want a sanity check before building the rest.
The two things I want to get right:
Verify the signature on the raw body, not the parsed JSON.
Don't process the same event twice, since providers retry.
Webhook node with "Raw Body" enabled, then a Code node:
const crypto = require('crypto');
const raw = (await this.helpers.getBinaryDataBuffer(0, 'data')).toString('utf8');
const sig = $input.first().json.headers['x-signature'];
const expected = crypto.createHmac('sha256', $env.WEBHOOK_SECRET).update(raw).digest('hex');
const ok = sig && sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
if (!ok) throw new Error('bad signature');
const event = JSON.parse(raw);
const store = $getWorkflowStaticData('global');
store.seen = store.seen || {};
if (store.seen[event.id]) return []; // already handled
store.seen[event.id] = Date.now();
return [{ json: event }];
Caveats I already know about:
- Header name and signature format differ per provider (some sign a timestamp too).
- Static data only persists for active (production) runs, not manual tests.
- It grows forever, so I'd prune old IDs or use a Data Table instead.
- I haven't run this exact code on n8n Cloud, so crypto/$env may be restricted there.
Questions: is static data or a Data Table the better place for the dedupe list? Anything about retries or out-of-order events I should handle before the "grant access" step?