# Overview





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](/docs/outbound), also available.

## Start here [#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](/docs/concepts/raw-bytes), signing, key rotation, and deduplication — the things that bite every webhook integration eventually.

<Cards>
  <Card icon="<ShieldCheckIcon />" title="Verify a signed request" href="/docs/inbound/verify" description="The basic recipe — start here." />

  <Card icon="<ShieldCheckIcon />" title="Provider verifiers" href="/docs/inbound/providers" description="Stripe, GitHub, Shopify, Twilio, Slack — ready-made, one factory each." />

  <Card icon="<BinaryIcon />" title="Raw bytes" href="/docs/concepts/raw-bytes" description="Why JSON.stringify(JSON.parse(body)) silently breaks verification. The single most common failure." />

  <Card icon="<PenLineIcon />" title="Signing schemes" href="/docs/inbound/signing" description="What verify() actually checks; v1 (HMAC) vs v1a (Ed25519)." />

  <Card icon="<KeyIcon />" title="Key rotation" href="/docs/inbound/key-rotation" description="The multi-secret window; JWKS for asymmetric verification." />

  <Card icon="<CopyIcon />" title="Deduplication" href="/docs/inbound/deduplication" description="Webhooks are at-least-once; the atomic dedup helper." />

  <Card icon="<PlugIcon />" title="Web adapters" href="/docs/web-adapters" description="Wire verification into Hono, Express, Fastify, NestJS, or any runtime." />
</Cards>

## What you get [#what-you-get]

| Feature                                                               | Status                                                                          | Where                                                                                                                              |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `Postel({ inbound })` factory with typed per-source configuration     | Ships                                                                           | `@postel/core`                                                                                                                     |
| HMAC-SHA256 (`v1`) signature verification (Standard Webhooks default) | Ships                                                                           | `Secret(...)` strategy                                                                                                             |
| Ed25519 (`v1a`) signature verification (Postel extension)             | Ships                                                                           | `PublicKey(...)` strategy                                                                                                          |
| JWKS consumer with caching and auto-refresh                           | Ships                                                                           | `Keyset({ jwksUri })` strategy                                                                                                     |
| Multi-secret rotation windows and cross-scheme migration              | Ships                                                                           | Pass `[Verifier, Verifier, …]`                                                                                                     |
| Ready-made Stripe / GitHub / Shopify / Twilio / Slack verifiers       | Ships                                                                           | [`Stripe(...)`, `GitHub(...)`, …](/docs/inbound/providers)                                                                         |
| Custom verification schemes — plug your own                           | Ships                                                                           | Implement the [`Verifier` contract](/docs/inbound/custom-verifiers)                                                                |
| Skip verification at a trusted boundary                               | Ships                                                                           | `Noop()` strategy                                                                                                                  |
| Constant-time signature comparison                                    | Ships                                                                           | (always on)                                                                                                                        |
| Structured `PostelError` subclasses for every failure mode            | Ships                                                                           | `SignatureInvalid`, `TimestampTooOld`, `MalformedHeader`, `UnknownKeyId`                                                           |
| Idempotency dedup with Postgres / SQLite / MySQL / in-memory adapters | Ships                                                                           | `@postel/pg`, `@postel/sqlite`, `@postel/mysql`, `InMemoryDedup`                                                                   |
| Hono, Express, Fastify, NestJS, Next.js framework adapters            | Ships                                                                           | `@postel/hono`, `@postel/express`, `@postel/fastify`, `@postel/nestjs`, `@postel/nextjs` — a verification gate over `@postel/http` |
| Bun adapter                                                           | Stub 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](/docs/outbound).

## The mental model [#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](/docs/inbound/signing) 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.
