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.


← Common Tasks  ·  Production Guide →