Webhook Verification Framework

Verify Every Webhook,
Across Every Platform.

Tern is a zero-dependency TypeScript framework for webhook signature verification. One SDK, every platform. No boilerplate, no fragile hand-rolled crypto.

MIT Licensed
$npm i @hookflo/tern
app/api/webhooks/route.ts
import { createWebhookHandler } from '@hookflo/tern/nextjs'
import { platform } from '../flags'
 
export const POST = createWebhookHandler({
  platform: await platform(),
  secret:  await process.env.WEBHOOK_SECRET!,
  handler: async (payload) => {
    // verified ✓ — handle your event
  }
})
19+
Platforms
0
Dependencies
Custom configs

Your webhook handler,
minus the boilerplate.

Clerk webhook verification today vs with Tern. Same security, a fraction of the code.

Before — Clerk (manual)~22 lines
import { Webhook } from 'svix'
import { headers } from 'next/headers'
export async function POST(req: Request) {
const SECRET = process.env.CLERK_WEBHOOK_SECRET
if(!SECRET) throw new Error('Missing secret')
const h = await headers()
const svix_id = h.get("svix-id")
const svix_ts = h.get("svix-timestamp")
const svix_sig = h.get("svix-signature")
if(!svix_id || !svix_ts || !svix_sig)
return new Response('Bad headers', { status: 400 })
const body = JSON.stringify(await req.json())
const wh = new Webhook(SECRET)
let evt
try { evt = wh.verify(body, {
"svix-id": svix_id, "svix-signature": svix_sig })
} catch (err) { return new Response('Invalid', {status:400}) }
// finally... handle event
}
After — With Tern8 lines. done.
+import { createWebhookHandler } from '@hookflo/tern/nextjs'
+import { platform } from '../flags'
+export const POST = createWebhookHandler({
+ platform: await platform(),
+ secret: process.env.WEBHOOK_SECRET!,
+ handler: async (payload) => {
// verified ✓ — handle your event
+ }
+})
−22
Lines deleted
+8
Lines added
0
Redeploys
Platforms
Vercel Feature Flags × Tern

Switch platforms with
a single flag flip.

No code change. No redeployment. Set your platform via Vercel feature flags — Tern reads them at runtime. Switch from Clerk to Stripe to GitHub without touching your codebase.

PLATFORM
clerk → stripe
that's it
Result
✓ Verified — no redeploy

Verification is
just the start.

Once your signature is verified, Tern can queue delivery, retry failures, recover from a dead-letter queue, and alert you when things go wrong — automatically.

01
Cross-platform verification
19 platforms. One SDK. HMAC-SHA256, Ed25519, SHA1 — Tern handles the algorithm. You write the handler.
02
Guaranteed deliveryNew
Queue every verified event. Retry on failure with backoff. Dead letter queue for anything exhausted. Bring Your Own Upstash account — your data, your stack.
03
Framework adapters
Next.js App Router, Cloudflare Workers, Express. Same verification logic, native integration for each runtime.
04
AlertingNew
Get notified on Slack or Discord when webhook events fail, land in the dead-letter queue, or get replayed. Know the moment something goes wrong — before your users do.
The full loop of inbound webhook handling — closed.Verify the signature. Queue the event. Retry on failure. Replay from the dead-letter queue. Get alerted when it matters. Every step handled, nothing left to wire up yourself.
Platforms
Frameworks

Works wherever
your code runs.

Purpose-built adapters for every major runtime. Same verification logic, native integration.

@hookflo/tern/nextjsstable
import { createWebhookHandler } from '@hookflo/tern/nextjs'

App Router adapter. Reads platform & secret from Vercel feature flags at runtime — no redeploy needed.

@hookflo/tern/expressstable
import { createWebhookMiddleware } from '@hookflo/tern/express'

Express.js middleware. Drop-in request verification before your route handler runs.

@hookflo/tern/cloudflarestable
import { createWebhookHandler } from '@hookflo/tern/cloudflare'

Cloudflare Workers adapter using the Web Crypto API. Edge-native, zero Node.js dependencies.

@hookflo/tern/honostable
import { createWebhookHandler } from '@hookflo/tern/hono'

Hono adapter for Node.js and edge runtimes. Tiny footprint, fast routing, and consistent request verification.

Core APIstable
import { WebhookVerificationService } from '@hookflo/tern'

Framework-agnostic core. Works in any runtime that supports the Web Crypto API — Deno, Bun, Node.js 18+.

Pick a platform,
get your snippet.

Real, working code for every supported platform and framework. Copy and paste — no adapting needed.

Platforms
import { createWebhookHandler } from '@hookflo/tern/nextjs';

// app/api/webhooks/stripe/route.ts
export const POST = createWebhookHandler({
  platform: 'stripe',
  secret:   process.env.STRIPE_WEBHOOK_SECRET!,
  handler:  async (payload) => {
    // ✓ verified — handle your event
    // payload.id can be stored for deduplication
    return { received: true };
  },
});
StripeNext.js signature verified

Works with the tools
you already use.

Verified implementations — not guesswork. Each platform is tested against real webhook payloads.

Stripe logo
Stripe
Clerk logo
Clerk
Linear logo
Linear
GitHub logo
GitHub
Shopify logo
Shopify
Polar logo
Polar
Dodo Payments logo
Dodo Payments
GitLab logo
GitLab
Vercel logo
Vercel
Replicate logo
Replicate
Razorpay logo
Razorpay
WorkOS logo
WorkOS
Fal AI logo
Fal AI
LemonSqueezy logo
LemonSqueezy
Paddle logo
Paddle
Doppler logo
Doppler
Sentry logo
Sentry
Grafana logo
Grafana
Standard Webhooks logo
Standard Webhooks

● verified    Custom config lets you verify any HMAC-based webhook without waiting for built-in support.

Built for the long run.

No magic, no bloat. Just the right abstractions in the right places.

01
Algorithm agnostic
HMAC-SHA256, SHA1, SHA512, or custom. Tern decouples platform logic from signing logic — add any platform without touching core code.
02
Zero dependencies
No svix, no axios, no lodash. Pure TypeScript using the platform's Web Crypto API. Smaller bundle, full auditability.
03
Fully type-safe
Comprehensive TypeScript types throughout. Catch wrong platform names and missing secrets at compile time, not in production.
04
Framework agnostic
Express, Next.js App Router, Cloudflare Workers, Deno — Tern normalizes the request interface so your code works everywhere.
05
Custom platform configs
Using a provider we don't support yet? Supply a signatureConfig object and verify any HMAC webhook — no library update needed.
06
Timing-safe by default
All comparisons use constant-time equality to prevent timing attacks. Replay protection via configurable timestamp tolerance is on by default.

Up and running
in three steps.

1
Install
One package, zero transitive dependencies. Works in Node.js, Next.js, Cloudflare Workers, Deno.
npm install @hookflo/tern
2
Verify with your platform
Pass the request, platform name, and secret. Tern handles header parsing, timing validation, and HMAC comparison.
import { WebhookVerificationService } from '@hookflo/tern' const result = await WebhookVerificationService .verifyWithPlatformConfig(request, 'clerk', process.env.WEBHOOK_SECRET) if (result.isValid) { // handle your event }
3
Use feature flags to switch platforms
With the Next.js adapter, pass platform from Vercel feature flags. Change platforms at runtime — zero code changes, zero redeployments.
export const POST = createWebhookHandler({ platform: await platform(), // from @vercel/flags secret: process.env.WEBHOOK_SECRET!, handler: async (payload) => { /* ... */ } })

Ready to delete
your webhook boilerplate?

Open source. MIT licensed. Built and maintained at Hookflo.