Web adapters

Next.js

NextjsWebAdapter binds typed Route Handlers over @postel/http — inbound.<source>.post, bindJwks, and an admin router binding.

View as Markdown

Next.js Route Handlers receive a Web Request and return a Response, so @postel/nextjs is a thin, typed facade over @postel/http: NextjsWebAdapter(postel) returns Route Handlers keyed by HTTP method — App Router route files export those directly.

Inbound webhook route

app/api/webhooks/vendor/route.ts
import { NextjsWebAdapter } from "@postel/nextjs";
import { postel } from "@/lib/postel"; // Postel({ inbound: { vendor: { verify: Secret(...) } } })

const nwa = NextjsWebAdapter(postel);

export const { POST } = nwa.inbound.vendor.post((result) => {
  return Response.json({ ok: true, type: result.event.type });
});

The source key is type-checked against the sources you configured, and result is typed to that source's schema output. On failure the gate short-circuits with the mapped HTTP status and your handler never runs; a non-PostelError bubbles as a 5xx. Use .on(method, handler, opts) to gate a body-bearing method other than POST (PUT | PATCH).

JWKS route

app/.well-known/webhooks-keys/route.ts
import { NextjsWebAdapter } from "@postel/nextjs";
import { postel } from "@/lib/postel";

export const { GET } = NextjsWebAdapter(postel).outbound.bindJwks();

Admin route

app/admin/[...path]/route.ts
import { NextjsWebAdapter } from "@postel/nextjs";
import { postel } from "@/lib/postel";

export const { GET, POST, PUT, PATCH, DELETE } = NextjsWebAdapter(postel).admin.bindAdminRoutes({
  authorize: (req) => check(req),
});

Low-level primitive

The facade is sugar over withWebhook(source, handler, opts?) — use it to gate a route file by hand without the facade:

import { withWebhook } from "@postel/nextjs";
import { postel } from "@/lib/postel";

export const POST = withWebhook(postel.inbound.vendor, (result) =>
  Response.json({ ok: true, type: result.event.type }),
);

On this page