webhook-signature
v0.1.0
Published
Unified webhook signature verification for Stripe, Paddle, GitHub, Slack, and many more providers
Downloads
11
Maintainers
Readme
webhook-signature
Unified webhook signature verification for multiple providers (Stripe, Paddle, GitHub, Slack, and more). One API, framework-agnostic core. Use in Node.js with Express, Fastify, Next.js, or any server.
Install
npm install webhook-signatureQuick example
import { verify } from 'webhook-signature';
const result = verify('stripe', {
rawBody: requestBody,
headers: { 'stripe-signature': signatureHeader },
}, { secret: process.env.STRIPE_WEBHOOK_SECRET });
if (!result.ok) {
console.error(result.error);
return;
}
// Proceed with handling the webhookSupported providers
| Provider | Option key | Notes |
|----------------|--------------|--------------------------------|
| Stripe | stripe | Use endpoint secret (whsec_...), maxAgeSeconds recommended |
| Paddle | paddle | HMAC-SHA256, timestamp in header |
| GitHub | github | X-Hub-Signature-256 |
| Shopify | shopify | X-Shopify-Hmac-SHA256 |
| Slack | slack | X-Slack-Signature, timestamp header |
| Lemon Squeezy | lemon_squeezy | X-Signature |
| Square | square | Secret is hex-encoded |
| Zoom | zoom | Same pattern as Slack |
| Dropbox | dropbox | X-Dropbox-Signature |
| Figma | figma | figma-signature |
| Linear | linear | linear-signature |
| Intercom | intercom | HMAC-SHA1, X-Hub-Signature |
| Zendesk | zendesk | Timestamp + body, base64 |
Getting the raw body
Verification uses the raw request body (before JSON parsing). If your framework parses the body, the signature will not match.
Express
Use express.raw() for the webhook route only:
import express from 'express';
import { expressWebhookVerifier } from 'webhook-signature';
const app = express();
// Webhook route: raw body first, then verifier, then your handler
app.post(
'/webhooks/stripe',
express.raw({ type: 'application/json' }),
expressWebhookVerifier('stripe', { secret: process.env.STRIPE_WEBHOOK_SECRET, maxAgeSeconds: 300 }),
(req, res) => {
// req.body is the raw Buffer here; parse if needed
res.sendStatus(200);
}
);
// Other routes can use express.json() as usual
app.use(express.json());Next.js (App Router)
Read the raw body with request.text() or request.arrayBuffer() before verifying:
import { verify } from 'webhook-signature';
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
const rawBody = await request.text();
const headers: Record<string, string> = {};
request.headers.forEach((v, k) => { headers[k] = v; });
const result = verify('stripe', { rawBody, headers }, { secret: process.env.STRIPE_WEBHOOK_SECRET! });
if (!result.ok) return NextResponse.json({ error: result.error }, { status: 401 });
// ...
}Generic Node.js (IncomingMessage)
If you have access to the raw body buffer (e.g. you pushed chunks into an array and concatenated), pass it and the headers map to verify().
API
verify(provider, payload, options)→{ ok: true } | { ok: false, error: string }getSupportedProviders()→ProviderId[]expressWebhookVerifier(provider, options)→ Express middlewarefastifyWebhookVerifier(provider, options)→ Fastify preHandler (use with fastify-raw-body or similar for raw body)
License
MIT
