Express
Verify webhooks in Express with ExpressWebAdapter — gated routes that capture raw bytes and set req.postel.
import { Postel, Secret } from "@postel/core";
import { config } from "./config.js";
export const postel = Postel({
inbound: {
vendor: {
verify: Secret(config.webhookSecret),
},
},
});Routing facade
ExpressWebAdapter(postel, app) registers gated routes by source key. Each route mounts express.raw({ type: () => true }) ahead of the gate for you, so the verifier sees the exact received bytes — don't put express.json() in front. The verified result is on req.postel.
import express from "express";
import { ExpressWebAdapter } from "@postel/express";
import { postel } from "./lib/postel";
const app = express();
const ewa = ExpressWebAdapter(postel, app);
ewa.inbound.vendor.post("/webhooks/vendor", (req, res) => {
res.json({ ok: true, type: req.postel.event.type });
});Source keys are type-checked. On failure the gate writes the mapped 4xx and your handler never runs; a non-PostelError is forwarded to next(err) (your error middleware / 500).
Other methods
.post is sugar for .on("POST", …). For a provider that delivers over PUT/PATCH, bind the method explicitly — body-bearing verbs (POST | PUT | PATCH) only, since the gate verifies a signature over the body:
ewa.inbound.vendor.on("PUT", "/webhooks/vendor", (req, res) => res.json({ ok: true }));JWKS
ewa.outbound.bindJwks(); // GET /.well-known/webhooks-keys
ewa.outbound.bindJwks("/keys", customProvider); // custom route + providerbindJwks() defaults the provider to postel.outbound.keys.publicJwks(); available when an outbound slot is configured.
Admin
ewa.admin.bindAdminRoutes("/admin", { authorize: (req) => checkAdminToken(req) });Low-level primitives
The facade wraps withWebhook / verifyWebhook (each returns [express.raw(...), gate]) and the fetchToExpress bridge:
import { verifyWebhook, fetchToExpress } from "@postel/express";
import { adminRouter } from "@postel/admin";
app.post("/webhooks/vendor", verifyWebhook(postel.inbound.vendor), (req, res) => res.send("ok"));
app.use("/admin", fetchToExpress(adminRouter(postel, { authorize: (req) => checkAdminToken(req) })));