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 get a webhook URL:

✅ Deployed: https://ingest.queue.baby/abc123

6. Set your Stripe secret

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

7. Point Stripe at your endpoint

In your Stripe Dashboard, add https://ingest.queue.baby/abc123 as a webhook 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 for ~7 days. Check the Dashboard to see every event, delivery status, and log.


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"
  }
})

Retries are automatic (~7 days with exponential backoff). This is not configurable per-drip — the system handles it.

Limits

Deduplication

export default (request, drip, env, rawBody) => {
  // Returns true if this key was already seen
  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.

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

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

Sandbox

Each execution runs in an isolated WebAssembly sandbox.

// What you CAN'T do:
fetch("https://evil.com")    // ❌ undefined
require("fs")                // ❌ undefined
import("module")             // ❌ not supported

// 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
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
Payload64KB128KB256KBCustom
Event History3 days7 days30 daysCustom
Dedupe keys10K50K250KCustom

429 Behavior

POST https://ingest.queue.baby/abc123
→ 429 Too Many Requests: Burst limit exceeded

Stripe, GitHub, Shopify — every major provider retries on 429. Your payloads aren't lost.

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.

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.

Failure Modes

Testing

# Fire a test Stripe webhook locally
q-baby test stripe

Sends a charge.succeeded event with amount 9900 ($99.00), customer cus_NAkzZ7ZdOeXbwR, and metadata with user_id and plan.

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    = "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}` }
  })
}