Inbound

Overview

Verify Standard Webhooks signed requests sent to your application. What ships, what to read in which order.

View as Markdown

The inbound half of Postel verifies signed webhook requests that other services send to your application. This is the half that's available today in @postel/core.

If you're sending webhooks to your customers — that's the outbound half, also available.

Start here

First time? Start with Verify a signed request — and if your sender is Stripe, GitHub, Shopify, Twilio, or Slack, the provider verifiers page has your one-liner. Before you ship to production, read raw bytes, signing, key rotation, and deduplication — the things that bite every webhook integration eventually.

What you get

FeatureStatusWhere
Postel({ inbound }) factory with typed per-source configurationShips@postel/core
HMAC-SHA256 (v1) signature verification (Standard Webhooks default)ShipsSecret(...) strategy
Ed25519 (v1a) signature verification (Postel extension)ShipsPublicKey(...) strategy
JWKS consumer with caching and auto-refreshShipsKeyset({ jwksUri }) strategy
Multi-secret rotation windows and cross-scheme migrationShipsPass [Verifier, Verifier, …]
Ready-made Stripe / GitHub / Shopify / Twilio / Slack verifiersShipsStripe(...), GitHub(...), …
Custom verification schemes — plug your ownShipsImplement the Verifier contract
Skip verification at a trusted boundaryShipsNoop() strategy
Constant-time signature comparisonShips(always on)
Structured PostelError subclasses for every failure modeShipsSignatureInvalid, TimestampTooOld, MalformedHeader, UnknownKeyId
Idempotency dedup with Postgres / SQLite / MySQL / in-memory adaptersShips@postel/pg, @postel/sqlite, @postel/mysql, InMemoryDedup
Hono, Express, Fastify, NestJS, Next.js framework adaptersShips@postel/hono, @postel/express, @postel/fastify, @postel/nestjs, @postel/nextjs — a verification gate over @postel/http
Bun adapterStub package today (__postelPackage only); use the factory directly meanwhile@postel/bun

If a row above isn't on this list, it's outbound — see Outbound.

The mental model

A verified webhook is just an HTTP POST with three extra headers:

webhook-id:        msg_2k4n8...
webhook-timestamp: 1716553200
webhook-signature: v1,h7P3wKjQz...

The receiver's job is to reject any request that doesn't match a small, well-defined set of conditions — and to do so in a way that doesn't leak timing information. Postel's verify() does this in five steps; failures throw subclasses of PostelError that name which step failed. See Signing schemes for the full pipeline.

You wire this once per source with the Postel({ inbound }) factory and call postel.inbound.<source>.verify(rawBytes, headers) from your route handler. That's the whole receiver surface.

On this page