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 initThis 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>yarn relay <command>bunx 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/clinpm install -D @restaq/cliyarn add -D @restaq/clibun add -d @restaq/cliDon't install both in the same project — they register the same relay bin, and only one wins the symlink.
Global environment variables
| Variable | Used by | Purpose |
|---|---|---|
STRIPE_WEBHOOK_SECRET | relay trigger stripe ... | Webhook secret for signing test Stripe events |
GITHUB_WEBHOOK_SECRET | relay trigger github ... | Webhook secret for signing test GitHub events |
CLERK_WEBHOOK_SECRET | relay trigger clerk ... | Webhook secret for signing test Clerk events |
SHOPIFY_WEBHOOK_SECRET | relay trigger shopify ... | Webhook secret for signing test Shopify events |
RESEND_WEBHOOK_SECRET | relay trigger resend ... | Webhook secret for signing test Resend events |
RESTAQ_BASE_URL | relay trigger, relay replay | Where 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
| Command | Purpose | Loads relay.ts | Requires app |
|---|---|---|---|
relay init | Interactive wizard: scaffold relay.ts + install packages | No | No |
relay migrate | Apply pending schema migrations | Yes | No |
relay dev | Run app dev server + tail executions | Yes (optional) | No |
relay trigger | Simulate a signed webhook | No | Yes (running) |
relay inspect | Show an execution's status, steps, logs | Yes | No |
relay replay | Replay a historical execution | Yes | Yes (running) |
relay events list | List recent executions | Yes | No |
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.