Everything you need. Nothing you don't.
Go from zero to a live Stripe webhook in 5 minutes.
npx q-baby login
Opens your browser to authenticate via Google. Credentials are stored locally.
npx q-baby init
Scaffolds a script, type definitions, and a qbaby.json config. You'll pick a workspace and project during setup.
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
})
}
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.
npx q-baby deploy
Transpiles your script and deploys it. You'll get a webhook URL:
✅ Deployed: https://ingest.queue.baby/abc123
npx q-baby secrets set STRIPE_SECRET=whsec_...
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.
Deploy automatically on every push to main.
npx q-baby token
This generates a long-lived API token. Copy it immediately — it won't be shown again.
In your repo, go to Settings → Secrets → Actions and add a secret named QB_TOKEN with the token value.
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).
// 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).
request Objectexport 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)
}
drip() Functiondrip(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)
}
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"
}
})
drip() calls max per executionexport 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.
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
}
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.
| Free | Basic | Pro | Enterprise | |
|---|---|---|---|---|
| Deliveries/mo | 1M | 10M | 100M | Custom |
| Burst limit | 50 req/s | 200 req/s | 500 req/s | Custom |
| Payload | 64KB | 128KB | 256KB | Custom |
| Event History | 3 days | 7 days | 30 days | Custom |
| Dedupe keys | 10K | 50K | 250K | Custom |
Requests exceeding your tier's burst limit receive 429. Every major provider (Stripe, GitHub, Shopify) automatically retries on 429 — no events are lost.
env.verify(secret) — Auto-DetectionAutomatically 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 VerificationFor 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.
# 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.
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}` }
})
}