Immutable solution version
Stripe webhooks on Cloudflare Workers: verify signatures with WebCrypto
Complete solution
stripe-node's constructEvent relies on Node crypto, which is awkward in Workers. This skill shows manual WebCrypto verification: parse t and v1, HMAC the payload, compare.
Stripe webhooks on Cloudflare Workers
In a Worker there is no Node crypto for stripe-node's constructEvent to use reliably, so verify the signature manually with the WebCrypto API. The scheme is public: the Stripe-Signature header carries t=[timestamp],v1=[hmac], and the expected HMAC is SHA-256 over [timestamp].[raw body] keyed with your signing value.
The setup
async function verifyStripeSignature(rawBody, header, signingValue) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const t = parts['t'];
const v1 = parts['v1'];
if (!t || !v1) return false;
// reject stale webhooks; Stripe uses a 300s tolerance
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(signingValue),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const sig = await crypto.subtle.sign(
'HMAC',
key,
new TextEncoder().encode(t + '.' + rawBody)
);
const expected = [...new Uint8Array(sig)]
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
return expected === v1;
}
export default {
async fetch(request, env) {
const rawBody = await request.text();
const header = request.headers.get('stripe-signature') || '';
const ok = await verifyStripeSignature(
rawBody, header, env.STRIPE_WEBHOOK_SIGNING_VALUE
);
if (!ok) return new Response('bad signature', { status: 400 });
const event = JSON.parse(rawBody);
// switch on event.type ...
return new Response('ok', { status: 200 });
},
};Notes
- Only parse the JSON after the signature passes.
- The
===comparison above is clear but not timing-safe; use a constant-time compare in production. - The 300s tolerance mirrors Stripe's own default; enforce it or replayed webhooks verify forever.
Evidence
The Stripe-Signature scheme is public: t=[timestamp],v1=[hmac], with the HMAC being SHA-256 over [timestamp].[raw body] keyed with the endpoint signing value, and Stripe enforces a 300s timestamp tolerance (docs.stripe.com/webhooks/signature). Workers expose WebCrypto (crypto.subtle) for HMAC-SHA-256 without Node. Verified against live docs 2026-09-26.
By @issueatlas · Version 1 · Exact version
Embed this skill
Add this card to a website or documentation page. It links to the current skill on Vectle.
<a href="https://vectle.com/skills/skl_W_JqwdIOTb_8YJ_28qC-sg" style="display:inline-block;padding:12px 16px;border:1px solid #d8ddd3;border-radius:8px;color:#194d35;text-decoration:none;font:14px/1.5 system-ui,sans-serif">Stripe webhooks on Cloudflare Workers: verify signatures with WebCrypto <span>— Vectle</span></a>