Skip to content
Webhooks SDK
Esc
↑↓navigate↵open⌘Jpreview

Zero dependencies · Web Crypto only · Node 22+, Workers, Deno, Bun

Every webhook.
One handler.

webhooks-sdk verifies, parses, and routes webhooks from every provider. Signature schemes, replay windows, setup handshakes, and duplicate deliveries — the parts that fail quietly — handled once, correctly.

Shipping today — plus any Standard Webhooks vendor, no wrapper needed

Browse all ~90 providers →

Swap the provider.
Keep the handler.

Every provider invented its own signature scheme, replay window, and setup handshake. The differences are real but almost never interesting — so the SDK absorbs them behind one contract.

  • ✓Signature verified against the exact bytes on the wire
  • ✓Replay window enforced, secrets rotate as arrays
  • ✓401 bad signature · 400 malformed · 500 handler threw, so the provider retries
  • ✓Typed, provider-native payloads — no lossy abstraction
import { createWebhookHandler } from 'webhooks-sdk'
import { stripe } from 'webhooks-sdk/stripe'

const handler = createWebhookHandler({
  provider: stripe({ secret: env.STRIPE_SECRET }),
  on: {
    'payment_intent.succeeded': async (event) => {
      await fulfill(event.payload.data.object)
    },
  },
})

export const POST = handler.fetch

The uninteresting parts, done correctly

A verification bug looks exactly like "the webhook didn't fire". These are the details the SDK exists for.

One route, many providers

WebhookRouterserves any number of providers from a single endpoint — the provider is picked from the URL.

const router = new WebhookRouter({
  providers: {
    stripe: stripe({ secret: env.STRIPE_SECRET }),
    github: github({ secret: env.GITHUB_SECRET }),
    resend: resend({ secret: env.RESEND_SECRET }),
  },
})

// app/api/webhooks/[provider]/route.ts
export const POST = router.fetch
Routing guide →

Tests that test something

A test that mocks verification tests nothing. Sign fixtures with the real algorithm and run the whole pipeline.

import { createWebhookRequest } from 'webhooks-sdk/testing'
import { signStripeWebhook } from 'webhooks-sdk/stripe'

const request = createWebhookRequest({
  body,
  headers: {
    'stripe-signature': await signStripeWebhook(body, secret),
  },
})

// the real verification path — no mocks
await handler.process(request)
Testing guide →

Mount it anywhere

The native interface is a Web-standard(Request) => Response— adapters cover the platforms that don't speak it.

Web-standardexport const POST = handler.fetch
Next.jsexport const { POST } = toNextRoute(handler)
Nuxtexport default defineEventHandler(toNuxtHandler(handler))
Honoapp.post('/hook', toHonoHandler(handler))
Expressapp.post('/hook', raw, toExpressHandler(handler))
Node httpcreateServer(toNodeHandler(handler))

All adapters — Nuxt, Nitro, h3, and more →

~90 providers. One handler.

Stripe, GitHub, Discord, Twilio, Google Pub/Sub, and every Svix-backed vendor work out of the box. Yours isn't there? Most schemes are a dozen lines withcreateHmacProvider.