Inbound

Overview

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

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. Before you ship to production, read the middle four — raw bytes, signing, key rotation, deduplication. They are 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, …]
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, RawBytesMismatchDetected
Idempotency dedup with Postgres / SQLite / MySQL / in-memory adaptersShips@postel/pg, @postel/sqlite, @postel/mysql, InMemoryDedup
Hono, Express, Fastify, NestJS framework adaptersShips@postel/hono, @postel/express, @postel/fastify, @postel/nestjs — a verification gate over @postel/http
Next.js, Bun adaptersStub packages today (__postelPackage only); use the factory directly meanwhile@postel/nextjs, @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