Documentation

Everything you need. Nothing you don't.

Getting Started

Go from zero to a live Stripe webhook in 5 minutes.

1. Login

npx q-baby login

Opens your browser to authenticate via Google. Credentials are stored locally.

2. Initialize

npx q-baby init

Scaffolds a script, type definitions, and a qbaby.json config. You'll pick a workspace and project during setup.

3. Write your first script

Open src/my-webhook.ts:

export default (request, drip, env, rawBody) => {
  // Verify the webhook signature
  if (!env.verify(env.secrets.STRIPE_SECRET)) return

  // Skip duplicate events
  if (env.dedupe(request.body.id)) return

  // Deliver to your API
  drip("https://api.myapp.com/webhook", request.body, {
    rate: 50
  })
}

4. Test it locally

npx q-baby dev

Starts a local dev server. In another terminal:

npx q-baby test stripe

Fires a test charge.succeeded event. You'll see the script execute and the delivery attempt in your terminal.

5. Deploy

npx q-baby deploy

Transpiles your script and deploys it. You'll see your webhook URL in the dashboard:

  ⚡ Deploying to my-workspace/default

  ✅ stripe-webhook deployed
     https://ingest.queue.baby/ws_abc123/qbaby_7f3a1b2c4d5e6f78
Webhook URL format: All workspaces use ingest.queue.baby/{workspace_id}/{route_id}. This URL is shown in your dashboard after deploying a script.

6. Set your Stripe secret

npx q-baby secrets set STRIPE_SECRET=whsec_...

7. Point Stripe at your endpoint

In your Stripe Dashboard, add your webhook URL as an endpoint. Select the events you care about.

That's it. Webhooks flow through qbaby, your script runs, and deliveries land at your API with automatic retries. Check the Dashboard to see every event, delivery status, and log.


CI / GitHub Actions

Deploy automatically on every push to main.

1. Create a token

npx q-baby token

This generates a long-lived API token. Copy it immediately — it won't be shown again.

2. Add it to GitHub Secrets

In your repo, go to Settings → Secrets → Actions and add a secret named QB_TOKEN with the token value.

3. Use the workflow

q-baby init scaffolds a .github/workflows/deploy.yml that deploys on push:

name: Deploy Webhooks
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npx q-baby deploy
        env:
          QB_TOKEN: ${{ secrets.QB_TOKEN }}

Manage tokens via npx q-baby tokens or the Dashboard (avatar menu → API Tokens).


Script Signature

// Every script exports a single function.
// It runs once per incoming webhook.

export default (request, drip, env, rawBody) => {
  drip("https://api.myapp.com/webhook", request.body)
}

Four arguments: request (the incoming webhook), drip (queue a delivery), env (secrets, variables, verification), and rawBody (original bytes for signature verification).

The request Object

export default (request, drip, env, rawBody) => {
  request.method   // "POST"
  request.path     // "/your-route-id"
  request.headers  // { "content-type": "application/json", ... }
  request.body     // parsed JSON payload (or raw string if not JSON)

  rawBody          // the exact bytes received, before JSON parsing
                   // use this for signature verification (never JSON.stringify)
}

The drip() Function

drip(destination, payload, options?)
export default (request, drip, env, rawBody) => {
  const billing   = "https://billing.myapp.com/fulfill"
  const analytics = "https://analytics.myapp.com/event"

  // Fan-out at independent rates
  drip(billing, request.body, { rate: 100 })
  drip(analytics, request.body, { rate: 10 })

  // Transform before sending
  drip("https://hooks.slack.com/T00/B00/xxx", {
    text: `New order: $${request.body.amount / 100}`
  })

  // Array shorthand: same payload, multiple destinations
  drip(["https://primary.myapp.com", "https://backup.myapp.com"], request.body)
}

Options

drip(destination, payload, {
  rate: 50,          // deliveries per second (default: your tier's max)
  method: "POST",    // HTTP method (default: POST)
  headers: {         // custom headers on the outbound request
    "X-Source": "queuebaby"
  }
})

Limits

Secrets & Variables

Inject configuration into your scripts via env.secrets and env.variables.

Secrets (encrypted at rest)

For API keys, signing secrets, and sensitive tokens. Values are AES-256-GCM encrypted in the database and never returned via API — only key names are visible.

q-baby secrets set STRIPE_SECRET=whsec_xxx  // Set or update
q-baby secrets list                          // List keys (values hidden)
q-baby secrets delete STRIPE_SECRET          // Remove

Variables (plaintext)

For non-sensitive configuration like URLs, feature flags, and environment labels.

q-baby variables set BILLING_URL=https://billing.myapp.com
q-baby variables list                        // Shows key = value
q-baby variables delete BILLING_URL

Usage in scripts

export default (request, drip, env, rawBody) => {
  // Encrypted secret — for API keys, signing keys
  if (!env.verify(env.secrets.STRIPE_SECRET)) return

  // Plaintext variable — for configuration
  const apiUrl = env.variables.BILLING_URL || "https://api.myapp.com"
  drip(apiUrl, request.body)
}

Deduplication

export default (request, drip, env, rawBody) => {
  // Returns true if this key was already seen (72h TTL in production)
  if (env.dedupe(request.body.id)) return

  // Safe to drip — this event hasn't been processed before
  drip("https://api.myapp.com/hook", request.body)
}

Call env.dedupe() once at the top. All drips below are safe. Deduplication window is 72 hours.

Logging

export default (request, drip, env, rawBody) => {
  console.log("Received:", request.body.type)   // visible in dashboard logs
  console.error("Something wrong")              // same output, just semantic
  console.warn("Watch out")                    // also captured
  console.info("FYI")                          // also captured

  // 100 log statements per execution, 5KB per statement
}

Sandbox

Each execution runs in an isolated WebAssembly sandbox (Wazero). No shared state between requests.

// What you CAN'T do:
fetch("https://evil.com")    // ❌ undefined
require("fs")                // ❌ undefined
import("module")             // ❌ not supported
drip("ftp://evil.com", d)    // ❌ only https:// allowed (http:// in dev only)

// What you CAN do:
drip("https://myapp.com", d) // ✅ queued and delivered by the engine
console.log("debug")         // ✅ captured in dashboard logs
env.dedupe("key")            // ✅ deduplication check
env.verify(secret)            // ✅ verify webhook signature
env.crypto.hmacSHA256(k, d)  // ✅ crypto primitives
env.secrets.MY_KEY           // ✅ encrypted secrets
env.variables.MY_VAR         // ✅ plaintext config
JSON.parse(str)              // ✅ standard JS built-ins work

Hard limits: 5MB RAM, 100ms execution time, no network access from inside the script.

Tier Limits

FreeBasicProEnterprise
Deliveries/mo1M10M100MCustom
Egress RPS25100500Custom
Payload64KB1MB1MBCustom
Retry window~4 days~7 days~7 daysCustom
Event History3 days7 days30 daysCustom
Dedupe keys10K50K250KCustom

See queue.baby for pricing details.

Ingress URLs

Your webhook URL format is: https://ingest.queue.baby/{workspace_id}/{route_id}

Ingress HostExample
ingest.queue.babyhttps://ingest.queue.baby/ws_abc123/qbaby_7f3a1b2c

All webhook traffic is protected by Cloudflare edge rate limiting and per-workspace bandwidth monitoring.

Webhook Signature Verification

env.verify(secret) — Auto-Detection

Automatically detects the webhook provider from headers and verifies the signature. Returns true if valid.

Supported providers: Stripe, Shopify, GitHub, Twilio, SendGrid. For other providers, use env.crypto.* for manual HMAC verification.

export default (request, drip, env, rawBody) => {
  if (!env.verify(env.secrets.STRIPE_SECRET)) return
  drip("https://api.myapp.com/webhook", request.body)
}

env.crypto.* — Manual Verification

For providers not auto-detected by env.verify(), use the crypto primitives with rawBody:

export default (request, drip, env, rawBody) => {
  const expected = env.crypto.hmacSHA256(env.secrets.MY_SECRET, rawBody)
  if (expected !== request.headers["x-custom-signature"]) return
  drip("https://api.myapp.com/hook", request.body)
}

Available primitives: env.crypto.hmacSHA256(key, data), env.crypto.hmacSHA1(key, data), env.crypto.sha256(data) — all return hex strings.

Domain Verification

qbaby verifies destination domains to prevent the platform from being weaponized as a DDoS amplifier. Verified domains get full-speed delivery. Unverified domains are throttled.

How it works

Without verification

Deliveries still work, but are throttled to 5 req/s per workspace and 500 req/hr globally across all workspaces targeting that domain.

CLI commands

q-baby domains                       // List verified domains
q-baby domains status                // Show verified + unverified
q-baby domains add api.example.com   // Start verification
q-baby domains verify api.example.com // Check DNS & verify
q-baby domains delete api.example.com // Remove a domain

Failure Modes & Retries

qbaby retries failed deliveries with exponential backoff: from 5 seconds to 24 hours. Free tier retries span ~4 days (14 attempts). Paid tiers retry for ~7 days (17 attempts).

Replay

Any ingested webhook can be re-processed from the Dashboard. Click into an event and hit Replay.

Use case: your script had a bug, you fixed it, now you want to re-process the events that failed.

Testing

Fire test webhooks at the local dev server (must be running via q-baby dev).

q-baby test                          // Default payload → first script
q-baby test stripe                   // Stripe fixture → first script
q-baby test github                   // GitHub fixture → first script
q-baby test shopify                  // Shopify fixture → first script
q-baby test stripe payments          // Stripe fixture → payments script
q-baby test --payload event.json     // Custom JSON file

Create fixtures/<name>.json in your project directory for custom provider fixtures.

Tip: The dev server also accepts Enter to fire a quick test event at your first script — no second terminal needed.

CLI Reference

CommandDescription
q-baby loginAuthenticate via browser (Google OAuth)
q-baby whoamiShow current auth identity and source
q-baby init [dir]Scaffold a new project
q-baby addAdd a new script to an existing project
q-baby dev [--port N]Start local dev server (default: 4567)
q-baby test [provider] [script]Fire test webhook at dev server
q-baby deployTranspile & deploy all scripts
q-baby statusShow workspace health & monthly quota
q-baby logsList recent events with delivery summary
q-baby logs <id>Drill into a specific event
q-baby secrets [set|list|delete]Manage encrypted secrets
q-baby variables [set|list|delete]Manage plaintext variables
q-baby domains [add|verify|status|delete]Manage domain verification
q-baby tokenCreate a new API token
q-baby tokensList API tokens
q-baby tokens revokeRevoke an API token
q-baby llmGenerate LLM context file

LLM Integration

npx q-baby llm

Generates a QBABY_LLM_RULES.md file with full qbaby API context — script signature, sandbox limits, common patterns, and the drip() API.

Drop it into .cursor/rules/, .github/copilot-instructions.md, or your system prompt. Your AI assistant will know how to write qbaby scripts.

Security

Full Example

export default (request, drip, env, rawBody) => {
  // Verify signature
  if (!env.verify(env.secrets.STRIPE_SECRET)) return

  const event = request.body

  // Skip duplicates
  if (env.dedupe(event.id)) return

  const slack = "https://hooks.slack.com/services/T00/B00/xxx"
  const db    = env.variables.API_URL || "https://api.myapp.com/webhooks/stripe"

  // Notify Slack
  drip(slack, {
    text: `💰 ${event.data.object.customer_email} — $${event.data.object.amount_total / 100}`
  })

  // Update your database at a controlled rate
  drip(db, event.data.object, {
    rate: 50,
    headers: { "Authorization": `Bearer ${env.secrets.API_KEY}` }
  })
}