Safe adoption

Built for Safe Adoption

Drop it in. Try it without sending. Back out cleanly if it isn't for you.

CommitCourier is designed to be added to an existing application in a small way, to let you confirm its behavior before any real delivery starts, and to be removed — without dragging your business schema with it — if it turns out not to fit. You don't have to trust it up front; you have to be able to evaluate it safely.

On this page:
Drops in

Add it to the Node.js / TypeScript app and PostgreSQL you already run — as a small, contained change.

Try without sending

Observe mode confirms production-equivalent behavior without making any outbound HTTP call.

Backs out cleanly

The dedicated tables, dispatcher and enqueue calls are easy to remove to return to your original setup.

Part 1Evaluate it safely
We get the hesitation

Adding a new library to a path that matters

Webhooks are an important path between your core systems and outside services. Being cautious about dropping an unproven library straight into that path is the right instinct. CommitCourier's goal isn't to win your full trust on day one — it's to keep the adoption decision small, safe, and reversible.

Reversible by design

Try small. Observe first. Enable when ready.

Adopting the library shouldn't be a one-way migration you can't undo. Each step is small and the previous one is still recoverable.

Add it in a small, contained change
   ↓
Observe — confirm behavior without sending
   ↓
Enable active delivery once you're convinced
   ↓
If it isn't a fit, remove just the dedicated parts
The point: don't turn library adoption into a large migration you can't walk back. Add small, confirm, then enable when you're convinced.
Small footprint

Added to your stack, not a rewrite of it

You don't build a new delivery platform from scratch; you add CommitCourier to the application and PostgreSQL you already run. It works alongside the code that emits webhooks rather than replacing your whole event architecture.

  • Added as an npm package
  • Uses your existing PostgreSQL
  • No extra infrastructure required — no Redis or Kafka
  • enqueue is added inside your existing business transaction
  • No need to migrate the whole app to a new eventing model
  • Change is contained to dedicated tables + a dispatcher
Observe mode

Don't trust it first. Observe it first.

Observe mode runs the same code path as real processing but makes no outbound HTTP call — events are recorded with the observed status instead of being sent. It's a staged-rollout step for verifying a production-equivalent path safely, not a toy dry-run.

Production-equivalent business logic
   ↓
enqueue in the same transaction
   ↓
Event recorded as "observed"
   ↓
No outbound HTTP is sent
// Evaluate safely: run the real code path, record what WOULD be sent — send nothing.
const relay = await createRelay({
  store,
  mode: "observe", // enqueue still rides your transaction; rows land as "observed"
});
// No outbound HTTP. Switch to the default (active) mode once you're convinced.
What you can confirm before sending anything:
  • Events are generated at the moments you expect
  • A rolled-back business transaction leaves no event behind
  • Payloads are correct
  • The event volume matches expectations
  • The right destination is selected
  • No unexpected events occur before going live
You don't have to trust it first. You can observe it first — with nothing sent.
Designed to leave cleanly

Easy to adopt. Designed to leave cleanly.

Reversibility is a first-class scenario, not an afterthought. The change you make at adoption is also the change you undo — so it's clear where to back out.

What adoption adds
  • CommitCourier's own tables
  • enqueue calls
  • the dispatcher
  • configuration
  • the npm package
Removing it
Stop the dispatcher
   ↓
Remove the enqueue calls
   ↓
Drop CommitCourier's tables
   ↓
Remove the config and package
CommitCourier's data lives in dedicated tables — your business tables aren't reshaped into a CommitCourier-specific format, and the app isn't deeply coupled to a new eventing model. Among libraries of this kind it's unusual to treat removal as a supported scenario; compared with typical Transactional Outbox libraries, the footprint is straightforward to back out.
Part 2Why it holds up
Security by default

Safe defaults, explicit escape hatches

Rather than claiming it's "secure," CommitCourier makes the safe setting the default and requires an explicit decision to loosen it. These are the representative measures — not a promise of perfect security.

Standard Webhooks signatures

HMAC-SHA256 over id.timestamp.body, with the timestamp signed. Verification accepts multiple secrets so a receiver works across a key rotation.

SSRF guard on by default

Private, loopback, link-local and cloud-metadata targets are blocked — validated against the DNS-resolved IP, which is pinned at connect time (DNS-rebinding aware).

Secrets encrypted at rest

A built-in AES-256-GCM cipher, or your own adapter for KMS / Vault. Without a cipher, startup prints an explicit plaintext warning.

Bounded by default

HTTP timeout, payload-size limit, retry / backoff caps and a replay safety cap — all set to safe defaults.

Careful with secrets & history

Signing secrets are not written into the delivery ledger, and stored response bodies are length-limited.

Explicit escape hatches

Unsafe-but-valid settings require an explicit acknowledgement — they are never applied silently.

Failure-aware design

Reliability is defined by failure behavior

Reliability isn't the number of features that work on the happy path — it's what happens when something fails. Failures are treated as a normal operational flow: recorded, retried, recoverable.

enqueue is fail-closed

It rides your business transaction. If the outbox row can't be written, your transaction fails too — you never emit a webhook for a change that didn't commit.

dispatch is fail-open

An async delivery failure never flows back into your business path. It is recorded, not thrown at your request.

retry → DLQ

Retryable failures back off and retry; a final failure lands in the dead-letter queue rather than vanishing.

Recovers after a crash

in-flight rows are reclaimed via a visibility timeout, so a worker crash mid-delivery doesn't strand events.

replay & cancel

Inspect the full delivery ledger, replay the DLQ, or cancel a pending row.

at-least-once, stated honestly

Delivery is at-least-once — not exactly-once. Every event carries an idempotency-key so the receiver can dedup.

Circuit breaker

Opt-in: after a threshold of consecutive failures a registered endpoint auto-disables so a dead receiver stops draining retries; re-enabling it resets the budget.

Reliability is decided by behavior on failure, not on success. Failures aren't exceptions here — they're an ordinary flow you can record, retry and recover from.
Evidence over promises

What we continuously verify

Rather than asserting "it's well tested," the project publishes what it checks. These run continuously in public CI — beyond a coverage number, they exercise failures, concurrency, and the post-publish package surface.

  • Unit tests
  • PostgreSQL integration tests (12 / 16 / 17)
  • Fault-path tests
  • Concurrency tests
  • Performance tests
  • Package export validation
  • ESM / CJS validation
  • Public API compatibility checks
  • Dependency audit
  • Mutation testing

The aim isn't a test count or a coverage figure — it's showing how many kinds of failure and boundary conditions are exercised. You can read the CI runs, the test suite and the security policy yourself.

Part 3Honest about the limits
Where it stands

Current status and limits — stated plainly

  • Pre-release (0.x) — the API may still change before 1.0
  • Not a mature project with large-scale production adoption yet
  • No third-party security audit yet
  • Not a service with an SLA or commercial support
  • External adopters and case studies are still being built up

That's exactly why there's Observe mode, a removable footprint, safe defaults, and a published test and security model. The aim is to make up for the track record it doesn't yet have with design, verification, and transparency — trust through evidence now, and through evidence and adoption over time.

Clear boundaries

What CommitCourier does and doesn't cover

CommitCourier does not make every webhook failure disappear. Being clear about the boundary of responsibility is part of being safe.

What it owns
  • Consistency between your business transaction and the webhook event record
  • Delivery, tracking, retries and recovery after the event is recorded
What you still own
  • Your receiver's own availability and business-logic bugs
  • Exactly-once effects / idempotency on the receiving side
  • PostgreSQL availability, backups and recovery
  • Correctness of the event data and your application logic
  • Your key management and access control
  • Full recovery from outages on the external service
Open to feedback

Help shape CommitCourier

CommitCourier is pre-release. Real adoption feedback, design reviews, security findings, and contributions to docs or adapters are all welcome. A point you got stuck on, a missing feature, an unclear explanation, or a small question — any of it can be shared on GitHub. For security issues, please use the private channel in SECURITY.md rather than a public issue.

Open the issue in public
   ↓
Write down the reasoning
   ↓
Work the fix
   ↓
Add tests
   ↓
Close it with the outcome recorded

Start by evaluating it — safely

Don't take it on trust. Add it small, observe it without sending, and enable it once you're convinced — then back out the dedicated parts if it isn't a fit.

Learn the webhook problem
   ↓
Review CommitCourier's design
   ↓
Watch the live demo
   ↓
Try it in Observe mode (nothing sent)
   ↓
Read the integration code
   ↓
GitHub / npm