Integration

Add reliable webhooks in five steps

This is the exact integration powering the live demo — the same code lives in server/courier.ts and server/routes.ts of this repo. Framework-agnostic; works with Express, Fastify, Nest, or none.

Drivers: pg and knex are supported as optional peers (Drizzle / Prisma too). Install whichever you use. Optional extensions: commitcourier/otel for traces & metrics, and commitcourier/accelerator/pg for LISTEN/NOTIFY low-latency wakeups.
npm install commitcourier pg
1

Create the tables

One idempotent migration adds the outbox / attempts / endpoints tables to your existing database.

import { Pool } from "pg";
import { postgresStore } from "commitcourier/store/pg";

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const store = postgresStore({ pool });

// Idempotent DDL — run once at deploy time.
await store.migrate();
2

Create the relay

Async: it validates config and fails fast if the tables are missing. All options shown with sane defaults.

import { createRelay, createConsoleLogger } from "commitcourier";

const relay = await createRelay({
  store,
  logger: createConsoleLogger(),
  retry: { maxAttempts: 6, backoff: "exponential", baseMs: 1_000, capMs: 60_000, jitter: 0.2 },
  delivery: { timeoutMs: 5_000 },
  ssrf: { blockPrivateRanges: true }, // ON by default
});
3

Enqueue inside your transaction

enqueue takes the transaction handle as its required first argument. This is the whole guarantee.

const client = await pool.connect();
try {
  await client.query("BEGIN");

  // ...your business writes on `client`...
  await client.query("INSERT INTO orders (id, amount) VALUES ($1, $2)", [orderId, amount]);

  // Rides the SAME transaction (fail-closed):
  await relay.enqueue(client, {
    eventType: "order.created",
    payload: { orderId, amount },
    endpoint: { url: "https://customer.example.com/webhooks", secret },
    idempotencyKey: orderId,
  });

  await client.query("COMMIT");   // order + webhook commit together
} catch (err) {
  await client.query("ROLLBACK"); // ...or vanish together
  throw err;
} finally {
  client.release();
}
4

Run the dispatcher

Delivers due rows in the background. Run it in-process or in a dedicated worker — several copies are safe.

const dispatcher = relay.createDispatcher({
  concurrency: 4,
  pollIntervalMs: 1_000,
  reclaimAfterMs: 300_000,
});
await dispatcher.start();
// graceful shutdown: await dispatcher.stop();
5

Verify on the receiving end

Standard Webhooks signatures — receivers verify with the bundled helper or any off-the-shelf library.

import { verifySignature } from "commitcourier";

// On the receiving end — verify before trusting the body:
const ok = await verifySignature({
  id: req.headers["webhook-id"],
  timestamp: req.headers["webhook-timestamp"],
  payload: rawBody,                       // raw bytes, before JSON.parse
  header: req.headers["webhook-signature"],
  secrets: [process.env.WEBHOOK_SECRET],
});

Watch every one of these steps execute against a real database on the live demo →