Cloudflare
Next.js App Router
Framework — Next.js

The webhook handler
Next.js was missing.

Purpose-built App Router adapter. Reads platform and secret from Vercel feature flags at runtime. Switch platforms without redeploying.

stablequeue support
$ npm i @hookflo/tern
app/api/webhooks/route.ts
// With queue: true — reads QSTASH_* from env automatically
// Next.js + Vercel only
import { createWebhookHandler } from '@hookflo/tern/nextjs'
export const POST = createWebhookHandler({
platform: 'stripe',
secret: process.env.WEBHOOK_SECRET!,
queue: true,
handler: async (payload) => {
// ✓ verified & queued — guaranteed delivery
return { received: true }
}
})

Three lines of code.
Four modes.

app/api/webhooks/route.ts
// Basic webhook verification — no queue
import { createWebhookHandler } from '@hookflo/tern/nextjs'
export const POST = createWebhookHandler({
platform: 'stripe',
secret: process.env.WEBHOOK_SECRET!,
handler: async (payload) => {
// ✓ verified — handle your event
return { received: true }
}
})
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
app/api/webhooks/route.ts
import { createWebhookHandler } from '@hookflo/tern/nextjs'
import { platform } from '../flags' // @vercel/flags
export const POST = createWebhookHandler({
platform: await platform(), // changes at runtime — no redeploy
secret: process.env.WEBHOOK_SECRET!,
handler: async (payload) => {
return { received: true }
}
})

Two calls,
one guarantee.

Stripe gets a 200 immediately. QStash handles delivery to your handler. Your endpoint returns fast — your logic runs reliably.

Call 1 — Platform → Tern
Stripe hits /api/webhooks Tern verifies signature (~5ms) Enqueues to QStash (~100ms) Returns 200 to Stripe immediately Stripe is done. Handler not run yet.
Call 2 — QStash → Tern (~1s later)
QStash delivers to same endpoint Tern verifies QStash signature Your handler runs Returns 200 to QStash Failure? QStash retries with backoff.

What you need.

# required always
WEBHOOK_SECRET=whsec_xxx
 
# required with queue: true
QSTASH_TOKEN=qstash_xxx
QSTASH_CURRENT_SIGNING_KEY=sig_xxx
QSTASH_NEXT_SIGNING_KEY=sig_xxx
 
# optional — for Vercel feature flags
FLAGS_SECRET=your_vercel_flags_secret

Ready to ship
reliable webhooks?

Open source · MIT licensed · Built at Hookflo