Skip to content

Repository files navigation

Restaq

Restaq is a durable webhook execution platform that helps developers build reliable, replayable, and observable event-driven backends.

Receiving a webhook is easy. Reliably processing everything that has to happen after it — retrying the parts that failed, not re-running the parts that didn't, replaying a historical event to debug it, seeing exactly what happened and why — is the hard part. Restaq is the runtime for that.

Category
Better Auth Authentication framework
Drizzle ORM
Hono Web framework
Restaq Webhook execution platform

Why

Webhook handlers are usually a pile of if statements with no memory of what already happened. A provider retries, and you double-charge a customer. A step three-quarters through a handler fails, and you re-run everything from scratch — or worse, nothing at all, silently. Debugging means grepping logs, because there's no record of what actually executed.

Restaq gives you:

  • Durable stepsctx.step.run(name, fn) checkpoints each unit of work. A retry resumes past whatever already succeeded; it never re-runs a completed step's side effects.
  • Automatic retries — a failed execution schedules its own retry with exponential backoff, up to a configurable cap, with no polling or queue infrastructure to run.
  • Replay & restartretryExecution resumes in place, restartExecution forces every step to re-run (for when the step logic itself was wrong, not just flaky), and replayExecution reprocesses a historical event as a brand-new execution without touching the original.
  • A real audit trail — every step attempt is a new row, never overwritten, so a step that failed twice before succeeding shows all three attempts. Every execution's log stream interleaves the runtime's own lifecycle events (retrying (attempt 2, scheduled)) with your handler's own logging, tagged so you can tell them apart.
  • Provider adapters with real signature verification — Stripe and GitHub today, each doing actual HMAC verification against the raw request body, not a stub.
  • Fully-typed event catalogsrelay.on('stripe.charge.succeeded', ...) autocompletes every event of the plugins you registered, and event.data is typed per event (event.data.object is a real Stripe.Charge).
  • Postgres, SQLite, or MySQL, auto-detected — pass a raw pg.Pool, better-sqlite3.Database, or mysql2.Pool straight into restaq({ database }) and Restaq detects the dialect and applies its own prefixed tables (restaq_executions, etc.), so they never collide with your application's own.
  • First-class local dev toolingrelay migrate applies the schema with one command; relay dev runs your app and tails live execution activity to the terminal.

Quickstart

pnpm add restaq better-sqlite3

restaq ships the engine and framework adapters (restaq/next-js, restaq/express, restaq/hono, restaq/nestjs) out of the box — better-sqlite3 is the fastest way to get a real database running locally, with no separate server.

// relay.ts — wiring only
import { restaq as createRestaq } from 'restaq';
import Database from 'better-sqlite3';
import { registerHandlers } from './relay.handlers';

export const restaq = createRestaq({ database: new Database('restaq.db') });
export type AppRelay = typeof restaq; // carries the typed event catalog forward
registerHandlers(restaq);
// relay.handlers.ts — handler logic lives here, not in relay.ts
import type { AppRelay } from './relay';

export function registerHandlers(relay: AppRelay): void {
  relay.on('order.placed', async (event, ctx) => {
    const payment = await ctx.step.run('charge-payment', async () => {
      return { orderId: event.data.orderId, amount: event.data.amount };
    });

    await ctx.step.run('send-confirmation', async () => {
      ctx.log.info('order confirmed', payment);
    });
  });
}
// try it — no HTTP, no plugin required
import { restaq } from './relay';

const execution = await restaq.ingest({
  id: crypto.randomUUID(),
  type: 'order.placed',
  data: { orderId: 'ord_1', amount: 4200 },
  receivedAt: new Date().toISOString(),
});

console.log(execution.status); // "completed" — charge-payment never re-runs on retry

From here: add provider plugins once you're ready to receive real, signature-verified webhooks; swap database for a pg.Pool or mysql2.Pool once you're ready for a multi-instance production deployment; import a framework adapter like restaq/next-js, restaq/express, restaq/hono, or restaq/nestjs to mount HTTP routes. See apps/docs (pnpm --filter docs dev, served on :3001) for the full installation guide, basic usage, and API reference.

Packages

This is a Turborepo monorepo:

Package Purpose
restaq The SDK: restaq, the user-facing types, and framework adapters (restaq/next-js, restaq/express, restaq/hono, restaq/nestjs). The only required install.
@restaq/core The engine: durable executions, steps, retries, replay, and the contracts (ExecutionStore, RelayPlugin) everything else builds on. Also owns database support — pass a raw Postgres/SQLite/MySQL client into restaq({ database }) and the dialect is auto-detected (built on Kysely, with hand-written versioned migrations). Pulled in automatically by restaq.
@restaq/plugin Plugin authoring kit: definePlugin + webhook signature helpers for building your own providers. Optional.
@restaq/stripe Stripe plugin: signature verification + a fully-typed catalog of every Stripe event (peer-depends on stripe). Optional.
@restaq/github GitHub plugin: signature verification + a fully-typed catalog of every GitHub webhook event. Optional.
@restaq/clerk Clerk plugin: Svix signature verification + typed event names for common Clerk webhook events. Optional.
@restaq/shopify Shopify plugin: HMAC signature verification + typed event names for common Shopify webhook topics. Optional.
@restaq/resend Resend plugin: Svix signature verification + typed event names for Resend email events. Optional.
@restaq/cli relay init, relay migrate, relay dev, relay trigger, relay inspect, relay replay, relay events list.
apps/docs The documentation site (Fumadocs). pnpm --filter docs dev serves it on port 3001.

Local Development

Setting up this monorepo (not just consuming the published packages) to hack on the runtime itself:

1. Prerequisites

  • Node >=22, pnpm >=9
  • @restaq/core's database contract tests want a container runtime (Docker, Colima, or Podman) for the Postgres/MySQL legs — see Testing below if you don't have one.

2. Clone the repo:

git clone <repo-url> && cd restaq
pnpm install

3. Build everything:

pnpm build

relay won't be on your $PATH the first time — pnpm install links it into the workspace root's node_modules/.bin, so pnpm exec relay ... (or pnpm exec from any workspace package) always resolves it.

4. Run the tests:

pnpm test

There's no bundled sample app in this repo — e2e/ is the workspace-linked, end-to-end check: it spawns a real HTTP server from a generated relay.ts, exercised via the built relay CLI binary and direct fetch calls, so it verifies actual local changes to every package before they'd reach a published release. See Testing for how it and the rest of the suite fit together.

Testing

pnpm test

Most tests are pure unit tests (no I/O). @restaq/core's store.contract.test.ts is a parametrized integration suite that runs the same ExecutionStore assertions against Postgres, SQLite, and MySQL — SQLite runs in-process (:memory:), while Postgres and MySQL spin up disposable containers via testcontainers, run migrations against them, and tear down after. No manual database setup required.

If your environment can't run containers (e.g. a CI runner without Docker-in-Docker, or this sandbox), set TEST_DATABASE_URL / TEST_MYSQL_URL to point at real instances instead — testcontainers is skipped entirely when they're set:

TEST_DATABASE_URL=postgres://localhost:5432/restaq_test TEST_MYSQL_URL=mysql://localhost:3306/restaq_test pnpm test

e2e/ runs a small number of integration tests against a real spawned app process: the CLI wizard's actual generated relay.ts, loaded by a minimal HTTP server, driven both by the built relay CLI binary and by direct fetch calls. No install step and no published packages required - the fixture is created inside e2e/ itself so it resolves the real workspace-linked builds.

Status

Restaq is pre-1.0 and under active development. The core runtime (events, executions, steps, logs, retries, restart, replay, dedup, concurrency-safe execution locking) is built and verified end-to-end against real signed webhook payloads and a real Postgres database — but it hasn't shipped a dashboard/observability UI yet (everything is currently inspectable via the API or psql), and retry/lock coordination is in-process only, so it doesn't yet coordinate across multiple server instances.

Releases

Packages

Used by

Contributors

Languages