Skip to content
Zap Studio
webhooks
Esc
navigateopen⌘Jpreview
On this page

OpenTelemetry

Native distributed tracing through @opentelemetry/api, with zero setup when no SDK is registered.

@opentelemetry/api is a required peer dependency of @zap-studio/webhooks. It’s a tiny, side-effect-free package that’s a no-op until an app registers a real SDK, so installing it costs nothing at runtime for consumers who never set one up — the same deal logger? already gives.

npm install @opentelemetry/api
yarn add @opentelemetry/api
pnpm add @opentelemetry/api
bun add @opentelemetry/api
deno add npm:@opentelemetry/api

There’s no option to configure — tracing is always on, and does nothing observable until your app registers an OpenTelemetry SDK:

import { createWebhookRouter } from "@zap-studio/webhooks";

const router = createWebhookRouter({ prefix: "/webhooks" });
router.register("/stripe", { schema: stripeEventSchema, handler });

export default { fetch: (request: Request) => router.handle(request) };

What Gets Instrumented

Every handle(request) call produces a SERVER delivery span named {method} {path} (the full request path, e.g. POST /webhooks/stripe), with http.request.method, url.path, and http.response.status_code attributes.

The incoming request’s traceparent header is extracted first, so the delivery span continues the sender’s trace instead of starting a new one — most webhook senders (Stripe, GitHub, etc.) that run their own OTel-instrumented infrastructure will already send one.

The route handler dispatch gets its own child INTERNAL span, named webhook.handler {path} (the matched route, e.g. /stripe), nested under the delivery span.

A non-2xx response — an unmatched route, a validation failure, a verification failure, or a handler error — marks the delivery span ERROR. When the handler itself throws, that exception is also recorded on the handler span via recordException.

Edge Runtime Caveat

Implicit context propagation — auto-linking the “active span” across await boundaries — depends on the host runtime supporting AsyncLocalStorage/async_hooks, which some edge runtimes don’t support reliably. Span creation, extraction, and attribute-setting always work everywhere regardless of this.

Log Correlation

Pair this with logger: once a span is active, ConsoleLogger automatically stamps trace_id/span_id onto every log line, so a delivery’s spans and logs line up in whatever backend you send them to.

Last updated on September 21, 2026

Was this page helpful?