Next.js

Webhook monitoring for Next.js.

Drop-in observability for App Router and Pages Router webhook handlers. Wraps your existing route — never modifies the request or response. Less than 3ms of overhead per webhook.

Step 1 — Install

Add the package

npm install @outworx/hooks
# or
pnpm add @outworx/hooks
# or
yarn add @outworx/hooks
Step 2 — Get your API key

Set environment variables

Grab your API key from the dashboard and add it to .env.local. Set the webhook secret for whichever provider you're wiring up too.

.env.local
# .env.local
OUTWORX_HOOKS_API_KEY=ow_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
Step 3 — Wrap your handler

App Router

For routes under app/. The wrapper exports a POST handler — signature is verified before your code runs, and duplicate retries (same event ID within 24h) short-circuit with the cached response.

app/api/webhooks/stripe/route.ts
// app/api/webhooks/stripe/route.ts
import { init } from '@outworx/hooks';
import { withWebhookMonitoring } from '@outworx/hooks/nextjs';

init({ apiKey: process.env.OUTWORX_HOOKS_API_KEY! });

export const POST = withWebhookMonitoring(
  {
    provider: 'stripe',
    signatureSecret: process.env.STRIPE_WEBHOOK_SECRET!,
    idempotencyKey: (_req, body) => (body as any).id,
  },
  async (req) => {
    const body = await req.json();
    // Signature verified, duplicates filtered.
    return Response.json({ received: true });
  }
);

Pages Router

For routes under pages/api/. Disable Next's body parser so the raw bytes reach the signature verifier.

pages/api/webhooks/stripe.ts
// pages/api/webhooks/stripe.ts
import { init } from '@outworx/hooks';
import { withWebhookMonitoring } from '@outworx/hooks/nextjs/pages';

init({ apiKey: process.env.OUTWORX_HOOKS_API_KEY! });

export const config = {
  api: { bodyParser: false }, // raw body needed for signature
};

export default withWebhookMonitoring(
  {
    provider: 'stripe',
    signatureSecret: process.env.STRIPE_WEBHOOK_SECRET!,
  },
  async (req, res) => {
    res.status(200).json({ received: true });
  }
);
Beyond Stripe

GitHub, Shopify, Clerk, and any custom provider

The same wrapper works for every provider. Built-in HMAC-SHA256 verification covers Stripe, GitHub, Shopify, Svix / Clerk, and Slack. For anything else, pass a signatureVerifier function and you're good.

app/api/webhooks/github/route.ts
// app/api/webhooks/[provider]/route.ts — one route, many providers
import { init } from '@outworx/hooks';
import { withWebhookMonitoring } from '@outworx/hooks/nextjs';

init({ apiKey: process.env.OUTWORX_HOOKS_API_KEY! });

export const POST = withWebhookMonitoring(
  {
    provider: 'github',
    signatureSecret: process.env.GITHUB_WEBHOOK_SECRET!,
    idempotencyKey: (_req, _body, headers) => headers['x-github-delivery'],
  },
  async (req) => {
    // ... handle GitHub event
    return Response.json({ ok: true });
  }
);
New in v1.5

Catch silent drops

A handler that returns 200 but never actually processes the event looks identical to a real success on every webhook tool — except ours. Set requireProcessingMark and call track.processed() when your business logic actually finishes:

app/api/webhooks/stripe/route.ts
export const POST = withWebhookMonitoring(
  {
    provider: 'stripe',
    signatureSecret: process.env.STRIPE_WEBHOOK_SECRET!,
    requireProcessingMark: true,           // ← opt in
  },
  async (req, { track }) => {
    const event = await stripe.webhooks.constructEventAsync(...);
    await chargeCustomer(event);
    track.processed();                     // ← explicit ack
    return Response.json({ received: true });
  }
);

Full guide: Silent drop detection.

Configuration reference

Full option list — including captureBody, metadata, idempotencyTtl, and the standalone signature verifiers — lives in the main SDK reference.

Ready to ship?

Free forever for 1,000 events/month. No credit card.

Get your API key