Examples
Real backend patterns and how Flux records and debugs them.
Full incident lifecycle
This walkthrough shows the complete production debugging loop — from a customer reporting a bug, to root-cause, to verified fix — using a Stripe webhook as the example.
Scenario: a customer reports their subscription did not activate after payment. You have no idea why.
Step 1 — Detect the failure
$ flux tail --filter path=/stripe_webhook --filter status=500
POST /stripe_webhook 500 32ms req:7c8d9e0f 09:14:22
└─ Error: Row not found: subscriptions WHERE customer_id = 'cus_ABC'
You have the failing request ID: req:7c8d9e0f.
Step 2 — Root-cause
$ flux why 7c8d9e0f
ROOT CAUSE Row not found: subscriptions WHERE customer_id = 'cus_ABC'
LOCATION stripe_webhook.ts:6
INPUT Stripe event: payment_intent.succeeded
customer_id: cus_ABC amount: 4900
DATA CHANGES subscriptions UPDATE 0 rows affected
SUGGESTION → Customer row does not exist at webhook time
Check that signup flow creates the row before payment
Step 3 — Confirm with full trace
$ flux trace debug 7c8d9e0f
Step 1/3 gateway
Input: POST /stripe_webhook
Output: { tenant_id: 't_123', event_type: 'payment_intent.succeeded' }
Time: 2ms
Step 2/3 stripe_webhook
Input: { customer_id: 'cus_ABC', amount: 4900 }
Error: Row not found: subscriptions WHERE customer_id = 'cus_ABC'
Time: 28ms
Step 3/3 (skipped — upstream error)
Now you understand the exact failure: the subscription row doesn't exist at webhook time, likely because signup and payment happen in the wrong order. You fix the signup function to create the subscription row before the payment is initiated.
Step 4 — Verify without deploying to production
$ flux incident replay 09:00..09:20
Replaying 7 requests from 09:00–09:20…
Side-effects: email off · webhooks stubbed
✔ req:1a2b3c4d POST /stripe_webhook 200 44ms
✔ req:7c8d9e0f POST /stripe_webhook 200 51ms ← was 500
7 replayed · 7 passing · 0 still failing ✔ incident resolved
Step 5 — Deploy and confirm live
$ flux deploy
$ flux tail --filter path=/stripe_webhook
POST /stripe_webhook 200 48ms req:d4e5f6a7
POST /stripe_webhook 200 51ms req:e5f6a7b8
✔ No more 500s
Total time from "customer reports bug" to "verified fix deployed": under 10 minutes.
Build a REST API
A typical POST /users function deployed to Flux:
// functions/create_user.ts
export default async function handler(req, ctx) {
const { email, name } = await req.json()
if (!email) {
return new Response(JSON.stringify({ error: 'email required' }), { status: 400 })
}
const user = await ctx.db.insert('users', { email, name, created_at: new Date() })
return new Response(JSON.stringify({ id: user.id }), { status: 201 })
}
After flux deploy and a few requests, every execution is recorded automatically. You can inspect any of them:
$ flux tail
POST /create_user 201 81ms req:4f9a3b2c
users.id=u_42 insert
POST /create_user 400 12ms req:a1b2c3d4
← Error: email required
$ flux why a1b2c3d4
ROOT CAUSE Validation error: email required
LOCATION create_user.ts:5
INPUT { name: "Alice" } ← email was missing
SUGGESTION → Add client-side validation or return clear error message
The mutation log for the successful request:
$ flux state history users --id u_42
users id=u_42 (1 mutation)
2026-03-10 09:14 INSERT email=a@b.com, name=Alice req:4f9a3b2c
Handle Stripe webhooks
Stripe webhooks are ideal for Flux because failures are hard to reproduce — the exact payload that caused the failure is preserved in the execution record.
// functions/stripe_webhook.ts
export default async function handler(req, ctx) {
const event = await ctx.stripe.verifyWebhook(req)
if (event.type === 'payment_intent.succeeded') {
const { customer_id, amount } = event.data.object
await ctx.db.update('subscriptions', { customer_id }, { status: 'active', paid_at: new Date() })
await ctx.email.send({ to: customer_id, template: 'payment_success', data: { amount } })
}
return new Response('ok', { status: 200 })
}
Debugging a failed webhook
A customer reports their subscription didn't activate after paying. With a traditional setup you'd need to find the Stripe event ID in their dashboard, correlate it to your logs, and hope the payload was logged somewhere. With Flux:
$ flux tail --filter path=/stripe_webhook --filter status=500
POST /stripe_webhook 500 32ms req:7c8d9e0f
└─ Error: customer_id not found in subscriptions
$ flux why 7c8d9e0f
ROOT CAUSE Row not found: subscriptions WHERE customer_id = 'cus_ABC'
LOCATION stripe_webhook.ts:6
INPUT Stripe event: payment_intent.succeeded
customer_id: cus_ABC
amount: 4900
DATA CHANGES subscriptions UPDATE 0 rows affected
SUGGESTION → Check that customer record was created before payment completes
The full Stripe payload is stored in the execution record — you can replay it:
$ flux incident replay 09:00..09:05
Replaying 3 Stripe webhook requests…
Side-effects: email sending off
✔ req:1a2b3c4d payment_intent.succeeded 200 44ms
✗ req:7c8d9e0f payment_intent.succeeded 500 32ms
└─ Still failing: customer_id not found
Debug a background worker
Async jobs enqueued via the Flux queue are treated as first-class requests — each job execution produces an execution record with its own ID.
// functions/send_onboarding_email.ts
export default async function handler(job, ctx) {
const { userId } = job.data
const user = await ctx.db.query('SELECT email, name FROM users WHERE id = $1', [userId])
if (!user) throw new Error(`User ${userId} not found`)
await ctx.email.send({
to: user.email,
template: 'welcome',
data: { name: user.name },
})
}
If the job fails silently (no 5xx, just wrong behaviour):
$ flux tail --filter function=send_onboarding_email
JOB send_onboarding_email done 220ms req:b3c4d5e6
JOB send_onboarding_email done 180ms req:c4d5e6f7
# Both show "done" — but emails weren't sent?
$ flux trace c4d5e6f7
queue.dispatch 2ms
└─ send_onboarding_email 178ms
├─ db.query(users) 3ms → { email: 'a@b.com', name: 'Alice' }
├─ email.send 170ms → { status: 'queued', provider: 'sendgrid' }
└─ response 1ms 200 OK
$ flux state history email_sends --filter job=c4d5e6f7
# No rows — email.send returned 200 but SendGrid rejected it silently
# → Check SendGrid logs with the provider message ID from the trace
Because the entire job execution is recorded, you can walk through exactly what the worker did and compare it against a successful run with flux trace diff.