Restaq
CLI

CLI

Manage Restaq from the command line with migrate, dev, trigger, inspect, replay, and more.

The relay command ships inside restaq itself — install the one package your project already needs, and the CLI comes with it.

Zero-install: scaffolding a brand new project

No project yet, nothing installed? One command:

npx restaq@latest init

This fetches restaq (and its bundled CLI) ephemerally, runs the interactive wizard, and scaffolds relay.ts + relay.handlers.ts — installing restaq for real along the way, so every command after this one runs locally.

Already have restaq installed?

The relay bin is available immediately, no separate @restaq/cli install needed:

pnpm exec relay <command>
npx relay <command>

Want the CLI as its own dependency instead (e.g. a CI image that only runs relay migrate, not the full app)? @restaq/cli is still published standalone and provides the identical relay bin:

pnpm add -D @restaq/cli

Don't install both in the same project — they register the same relay bin, and only one wins the symlink.

Global environment variables

VariableUsed byPurpose
STRIPE_WEBHOOK_SECRETrelay trigger stripe ...Webhook secret for signing test Stripe events
GITHUB_WEBHOOK_SECRETrelay trigger github ...Webhook secret for signing test GitHub events
CLERK_WEBHOOK_SECRETrelay trigger clerk ...Webhook secret for signing test Clerk events
SHOPIFY_WEBHOOK_SECRETrelay trigger shopify ...Webhook secret for signing test Shopify events
RESEND_WEBHOOK_SECRETrelay trigger resend ...Webhook secret for signing test Resend events
RESTAQ_BASE_URLrelay trigger, relay replayWhere to POST webhook deliveries; defaults to http://localhost:3000

Note there's no DATABASE_URL here — every command that touches the database (migrate, dev, inspect, replay, events list) loads your relay.ts directly and uses whatever client it configures, so the CLI itself never needs a connection string.

Commands

CommandPurposeLoads relay.tsRequires app
relay initInteractive wizard: scaffold relay.ts + install packagesNoNo
relay migrateApply pending schema migrationsYesNo
relay devRun app dev server + tail executionsYes (optional)No
relay triggerSimulate a signed webhookNoYes (running)
relay inspectShow an execution's status, steps, logsYesNo
relay replayReplay a historical executionYesYes (running)
relay events listList recent executionsYesNo

Dev-secret gotcha

relay trigger signs payloads with <PROVIDER>_WEBHOOK_SECRET and requires it to be set — it exits with STRIPE_WEBHOOK_SECRET must be set. otherwise. Your app verifies with whatever secret its config passed to the plugin. Signatures only match when both sides use the same value:

# ✅ Same secret on both sides — signatures match
STRIPE_WEBHOOK_SECRET=whsec_dev relay dev
STRIPE_WEBHOOK_SECRET=whsec_dev relay trigger stripe charge.succeeded --data '...'

# ❌ Different (or missing on one side) — verification fails with 401
STRIPE_WEBHOOK_SECRET=whsec_dev relay dev
STRIPE_WEBHOOK_SECRET=whsec_other relay trigger stripe charge.succeeded --data '...'

A common dev pattern is to have your config fall back to a fixed dev-only secret when the env var is unset (failing closed in production), so you only need to export the secret for relay trigger:

function requireWebhookSecret(envVar: string, devFallback: string): string {
  const secret = process.env[envVar];
  if (secret) return secret;
  if (process.env.NODE_ENV === 'production') {
    throw new Error(`${envVar} must be set in production`);
  }
  return devFallback;
}

export const restaq = createRestaq({
  database: new Database('restaq.db'),
  plugins: [stripe({ webhookSecret: requireWebhookSecret('STRIPE_WEBHOOK_SECRET', 'whsec_dev') })],
});

See relay trigger for per-provider examples and relay dev for tailing live executions while testing.

On this page