Get started

Why Postel

The positioning, the tradeoffs, and the design rationale. Why a library, not a service. Why polyglot. Why reliability and security are the differentiator.

Most teams reach for webhooks expecting a weekend feature and find a long tail instead: retries, replay, key rotation, idempotency, multi-secret windows, JWKS, raw-bytes preservation. Postel exists to absorb that tail — as a library you embed, not a service you operate.

This is the long-form rationale. For a 30-second fit check, use Is Postel for me? instead.

The problem

Teams who need outbound webhooks today choose between:

  • A hosted service — Svix, Hookdeck Outpost — solves the problem comprehensively but adds operational footprint (Postgres + Redis + a separate dispatcher process) and costs $0→$490/mo cliff for hosted.
  • A hand-rolled worker on Sidekiq, Oban, or BullMQ — the queue handles retries; everything else (signing, idempotency, replay, key rotation, JWKS, multi-secret windows, dedup, raw-bytes preservation) is reimplemented every time.

The Standard Webhooks specification covers the wire format and ships signing helpers in nine languages, but explicitly leaves delivery, retry, key management, replay, and operational tooling to implementers. There's a gap between "signs and verifies" and "production-grade delivery layer" that the consortium doesn't fill.

Postel sits in that gap.

The positioning

Svix is for when webhooks are your product. Postel is for when webhooks are a feature of your product.

Postel does not compete with Svix or Hookdeck on customer-facing webhook portals, multi-region delivery, or 99.999% uptime SLAs. It targets a different audience: teams who want to add reliable outbound webhooks to an existing application without standing up a separate service, and single-binary OSS products that cannot run a Postgres + Redis + service sidecar in the first place.

Comparison

Svix self-hostedHand-rolled (BullMQ/Sidekiq)Standard Webhooks lib onlyPostel
Sender — outbox, retries, replay⚠️ DIY
Receiver — verification, dedup⚠️ DIY⚠️ Signing only
Runs in your app process
Requires Redis / brokerUsually yes
Requires separate dispatcher processOften
Customer-facing portal as packaged product❌ (use Svix)
Multi-region / 99.999% SLA✅ (hosted)⚠️ DIYn/a❌ (use Svix)
Multi-language portsJava, Go, Python, Rust, Kotlin, Ruby, C#, PHP (client libs only)n/a✅ 9 langs (signing only)TS today; Go/Py/Rust on roadmap (full lib)
CostSelf-host infra + opsEngineer timeFreeFree (OSS)

If you need anything in the "use Svix" rows, use Svix. Postel won't grow into them.

Four design choices

1. Library, not service

Postel runs inside the host application against the host application's existing relational database (Postgres, MySQL, SQLite, …). Outbox inserts join the host's existing transaction — send() commits or rolls back atomically with the host's business writes. The transactional-outbox guarantee without extra connections, brokers, or a sidecar process.

This is the inverse of Svix's bet. Svix wins when webhooks are the product and the operational footprint of a separate service is justified. Postel wins when adding webhooks to an existing app is a feature one engineer should be able to ship in an afternoon.

Postel will never have a hosted offering, never run a separate dispatcher process, never require Redis or a message broker, never ship a customer-facing portal as a packaged product. If you need any of that, use Svix or Hookdeck Outpost.

2. Conformance, enforced

The reliability and security guarantees Postel makes have to hold across every port — TypeScript today, Go and Python and Rust tomorrow. That requires more than docs. Every behavior — sender, receiver, retry, replay, dedup, key rotation — is captured as a testable scenario, and the cross-port contract is enforced by a compliance test suite (@postel/compliance) that runs against any HTTP receiver claiming Standard Webhooks compliance. Every Postel port passes the same suite at the same version before it can ship.

The suite is the boundary between CONTRACT (everything every port must do) and PORT-SPECIFIC (mechanism each port is free to choose). What the suite tests is contract; what it doesn't is implementation detail. See Reference → Specs & standards.

3. Polyglot from the start

The TypeScript port ships first because Node + Postgres is the largest existing footprint and the receiver story is the smallest plausible cut. But the contract is language-agnostic: the wire format is AsyncAPI 3.0, the DB schema is SQL DDL, the capability behaviors are markdown specs, and the compliance suite drives any conformant receiver. Go, Python, and Rust ports follow on the same schedule, each gated on passing the suite. See Polyglot.

The bet: a small narrow contract enforced by the compliance suite scales further than per-port reinvention or single-language lock-in.

4. Standard Webhooks-aligned, not divergent

Postel implements the Standard Webhooks wire format end-to-end. The HMAC v1 signature scheme works unchanged. Ed25519 (v1a) for asymmetric verification is a Postel extension layered on top — additive, never breaking. JWKS publication is a one-liner. The webhook-spec-version header signals capability without breaking older receivers.

When we deviate, we do so via the multi-secret rotation window: producers can sign with both old and new keys during overlap, and receivers accept either. No flag day.

Operating principles

  • OSS license. MIT or Apache-2.0 (decided before 1.0).
  • No "open-core." Every feature in the capability specs ships in OSS, forever. The funding model — sponsorships, support contracts — is separate and never feature-gates.
  • Maintainer-led governance with clear contribution guidelines. Public roadmap. The compliance suite is the binding contract.
  • Standard Webhooks consortium engagement. We pursue "delivery layer" reference implementation status with the consortium, with this library as the reference.

What 1.0 looks like

A reasonable observer answers yes to all of:

  1. Can I add webhooks to my existing relational-DB app (Postgres, MySQL, SQLite, …) without bringing up Redis or a service?
  2. Does it handle key rotation with overlap windows out of the box?
  3. Can I publish a JWKS endpoint with one line?
  4. Is replay a first-class API verb?
  5. Does the TypeScript implementation pass @postel/compliance end-to-end?
  6. Are the receiver verifier errors actionable?
  7. Does the multi-tenant scheduler isolate noisy neighbors by default?
  8. Is the "Why not a service?" answer obvious from the docs?
  9. Are the wire format, DB schema, and capability specs documented well enough that the first community port is plausible — and validated by passing the compliance suite?

If all yes → 1.0. Otherwise it isn't done.

On this page