Skip to content

Idempotent Handlers

Chronos guarantees at-least-once delivery. That means your handler may run more than once for the same job:

  • A timeout occurs but your handler actually completed (late result report)
  • A network partition prevents the result from reaching Chronos
  • Push delivery: your endpoint processed the request but responded too slowly

If your handler charges a credit card, sends an email, or creates a record, running it twice without protection produces duplicates.

Field Scope Use when
ctx.jobId Same across retries Side effects that must happen once per job (payments, emails, record creation)
ctx.executionId Unique per attempt Per-attempt tracking (logging, metrics, attempt-specific records)

Use jobId by default. It stays the same across retries, so if a retry fires after a side effect already succeeded, downstream systems detect the duplicate and skip it.

Use executionId only when each retry attempt should be treated as distinct (per-attempt logs, metrics, or audit records).

Pass the job ID to APIs that support idempotency:

chronos.worker.handle<{ customerId: string; amount: number }>('charge', async (ctx) => {
await stripe.charges.create({
amount: ctx.payload.amount,
currency: 'usd',
customer: ctx.payload.customerId,
idempotency_key: ctx.jobId,
});
return { charged: true };
});

Stripe (and many other payment APIs) will reject duplicate charges with the same idempotency key. Because jobId stays the same across retries, a timed-out attempt that actually succeeded won’t cause a second charge.

Not every handler needs dedup. If the operation is naturally idempotent, you’re already safe:

  • Read-only operations: fetching data, generating reports from current state
  • Full-state overwrites: UPDATE users SET last_seen = now() WHERE id = ?
  • Upserts: INSERT ... ON CONFLICT DO UPDATE with the same data
  • Deleting by ID: deleting something that’s already gone is a no-op

The rule: if running twice produces the same end state with no extra side effects, you’re fine without explicit dedup.