Lifecycle Hooks
Use before, after, and onError hooks for logging, metrics, and centralized error handling.
webhooks exposes global before, after, and onError hooks for cross-cutting behavior. Hooks are shared interception points around route execution — they keep handlers focused on business logic while logging, metrics, and error policy live in one place.
Add Global Hooks
Configure hooks once in createWebhookRouter — they apply to every route. Each of before and after accepts a single hook or an array; hooks run sequentially in registration order.
import { createWebhookRouter } from "@zap-studio/webhooks";
const router = createWebhookRouter({
before: (ctx) => {
console.log("incoming", ctx.path);
},
after: (_ctx, response) => {
console.log("status", response.status);
},
onError: (error) => Response.json({ error: error.message }, { status: 500 }),
});
Every hook receives the webhook context — { request, rawBody, path } — where request is the incoming Web API Request (body already consumed by the router), rawBody holds the exact body bytes, and path is the matched route key.
Hook signatures (exported from webhooks):
BeforeHook—(ctx: WebhookContext) => Promise<void> | voidAfterHook—(ctx: WebhookContext, response: Response) => Promise<void> | voidErrorHook—(error: Error, ctx: WebhookContext) => Response | undefined, sync or async
Add Route-Level Hooks
Register before and after on a single route when one endpoint needs extra behavior beyond the global hooks.
import { z } from "zod";
router.register("/github/push", {
schema: z.object({ ref: z.string() }),
before: (ctx) => {
console.log("route-before", ctx.path);
},
handler: () => Response.json("ok"),
after: (_ctx, response) => {
console.log("route-after", response.status);
},
});
Understand Execution Order
For a successful request, the router runs:
- global
beforehooks - route
beforehooks verify- body parsing and schema validation
- handler
- route
afterhooks - global
afterhooks
after hooks only run when the handler completes successfully. If any step throws, execution stops and error handling takes over.
Centralize Error Handling
When any hook, the verifier, or the handler throws, the router calls onError with the error and the webhook context. Return a Response to override the default response, or return undefined to fall back to the default 500 with { error: message }.
import { createWebhookRouter, VerificationError } from "@zap-studio/webhooks";
const router = createWebhookRouter({
onError: (error, ctx) => {
console.error("webhook failed", ctx.path, error.message);
if (error instanceof VerificationError) {
return Response.json({ error: "invalid signature" }, { status: 401 });
}
return undefined; // default 500 response
},
});
onError gives you one place to standardize error shape and status codes, and to map provider or framework errors consistently. Validation failures do not reach onError — the router returns a 400 with the validation issues directly.