Skip to content

Inbound handlers

Inbound webhooks receive HTTP requests from providers and turn them into Layeron webhook events.

Use handler when every event should go through the same function:

Terminal window
const vendor = webhooks.custom({
name: "vendor",
path: "/webhooks/vendor",
handler: async (event, ctx) => {
await ctx.log("Vendor webhook received", {
eventId: event.id,
eventType: event.eventType,
})
await routeVendorEvent(event.payload)
},
})

The handler receives:

  • event.id: Layeron event ID.
  • event.eventType: Type discovered from type, event, or event_type in the payload.
  • event.externalEventId: Provider event ID when the payload includes id, event_id, or eventId.
  • event.payload: Parsed JSON payload, or the raw body as text.
  • event.headers: Request headers.
  • event.signatureStatus: verified, failed, or skipped.

Use on when each provider event type has its own handler:

Terminal window
const stripe = webhooks.stripe({
path: "/webhooks/stripe",
secret: secret("STRIPE_WEBHOOK_SECRET"),
on: {
"customer.subscription.created": async (event) => {
await createSubscription(event.payload)
},
"customer.subscription.deleted": async (event) => {
await cancelSubscription(event.payload)
},
},
})

If an event type has no matching on handler, the event is still recorded.

Providers often retry the same event. Add dedupe to keep the first accepted event and mark repeats as duplicates:

Terminal window
const vendor = webhooks.custom({
name: "vendor",
path: "/webhooks/vendor",
dedupe: {
key: "body.event_id",
ttl: "7d",
},
handler: async (event) => {
await processVendorEvent(event.payload)
},
})

The key path reads from the parsed body. Use body.id, body.event_id, or any stable provider field.

For custom logic, provide a function:

Terminal window
dedupe: {
key: async (event) => {
return `${event.provider}:${event.externalEventId}`
},
}

For inbound requests:

  • A valid request is accepted with HTTP 202.
  • A duplicate request is accepted with HTTP 202 and deduped: true.
  • A signature failure returns HTTP 400.
  • Handler work is queued and recorded separately from the provider response.