Receiving
Inbound is the part every integration gets wrong, and it gets wrong in the same four ways. The SDK handler does all four for you.
import { createWebhookHandler } from "@atrixdigital/switchboard"
export const POST = createWebhookHandler({
secret: process.env.SWITCHBOARD_WEBHOOK_SECRET!,
seen: (id) => db.deliveries.exists(id),
onEvent: async ({ event, meta }) => {
if (event.kind !== "inbound") return
await db.deliveries.insert(meta.deliveryId)
await queue.push(event) // do the WORK off the request
},
onError: (error, ctx) => logger.error({ error, id: ctx.meta.deliveryId }),
})It takes a Request and returns a Response, so it drops into a Next.js route handler, Elysia, Hono, Bun.serve or plain Node without knowing which.
If you are not using the SDK#
Implement these four yourself.
Verify the secret#
It is the last path segment, not a header — the engine’s webhook producer cannot attach headers. Compare in constant time.
Fail closed when your configured secret is empty
Dedupe on X-Switchboard-Delivery#
Delivery is at-least-once. That is the guarantee, not a caveat: the alternative is losing messages. The same event arrives again whenever a delivery succeeded and the acknowledgement was lost.
A unique index on it is the whole implementation
Never answer 5xx for a bug in your own handler#
A non-2xx tells Switchboard to retry, so one bug becomes hours of redelivery and eventually a dead letter for a message that was fine. Log it, answer 200, fix the bug — the payload stays replayable from /v1/deliveries.
Do answer 5xx when your dependency is down and a redelivery would genuinely help — a database you cannot reach to dedupe against, for instance.
Answer fast; do the work afterwards#
Events arrive in bursts. A handler that runs a database write and an LLM call before returning 200 holds the delivery worker’s connection for the duration. Enqueue and return.
Event kinds#
inbound, delivery, instance, ignored. The parser never throws — an unfamiliar shape becomes ignored with a reason rather than an exception, precisely so a surprise cannot become a retry storm.