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 see your webhook URL in the dashboard:
⚡ Deploying to my-workspace/default
✅ stripe-webhook deployed
https://ingest.queue.baby/ws_abc123/qbaby_7f3a1b2c4d5e6f78
ingest.queue.baby/{workspace_id}/{route_id}. This URL is shown in your dashboard after deploying a script.
npx q-baby secrets set STRIPE_SECRET=whsec_...
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.
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 executionhttps:// destination URLs are allowed in production. http:// is accepted in the CLI dev server for local testing, but rejected by the engine. Other protocols are rejected.Inject configuration into your scripts via env.secrets and env.variables.
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
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
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)
}
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.
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
}
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.
| Free | Basic | Pro | Enterprise | |
|---|---|---|---|---|
| Deliveries/mo | 1M | 10M | 100M | Custom |
| Egress RPS | 25 | 100 | 500 | Custom |
| Payload | 64KB | 1MB | 1MB | Custom |
| Retry window | ~4 days | ~7 days | ~7 days | Custom |
| Event History | 3 days | 7 days | 30 days | Custom |
| Dedupe keys | 10K | 50K | 250K | Custom |
See queue.baby for pricing details.
Your webhook URL format is: https://ingest.queue.baby/{workspace_id}/{route_id}
| Ingress Host | Example |
|---|---|
ingest.queue.baby | https://ingest.queue.baby/ws_abc123/qbaby_7f3a1b2c |
All webhook traffic is protected by Cloudflare edge rate limiting and per-workspace bandwidth monitoring.
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. 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 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.
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.
_qbaby.<your-domain> with value qbaby-verify=<token>q-baby domains add or in the DashboardDeliveries still work, but are throttled to 5 req/s per workspace and 500 req/hr globally across all workspaces targeting that domain.
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
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).
exhausted, visible in Dashboard413drip() rejects non-https URLs in production (http:// is allowed in the CLI dev server for local testing only)Any ingested webhook can be re-processed from the Dashboard. Click into an event and hit Replay.
env.dedupe() is bypassed during replay — deduplication won't block re-processingUse case: your script had a bug, you fixed it, now you want to re-process the events that failed.
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.
| Command | Description |
|---|---|
q-baby login | Authenticate via browser (Google OAuth) |
q-baby whoami | Show current auth identity and source |
q-baby init [dir] | Scaffold a new project |
q-baby add | Add 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 deploy | Transpile & deploy all scripts |
q-baby status | Show workspace health & monthly quota |
q-baby logs | List 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 token | Create a new API token |
q-baby tokens | List API tokens |
q-baby tokens revoke | Revoke an API token |
q-baby llm | Generate LLM context file |
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.
https://. TLS certificates are verified — self-signed certs will fail delivery. http:// is only allowed in the CLI dev server for local testing.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}` }
})
}