Deduping usage events with idempotency keys

If a usage event can be sent twice, it will be. An idempotency key turns "at least once" delivery into "counted exactly once".

The problem

Your server sends an event, the request times out, and you retry. Did the first one land? Without a key, the meter can't tell, so a timeout becomes a double charge. Retries come from HTTP clients, queue redelivery, cron reruns and deploys mid-request.

The fix

Attach a key that identifies the billable thing, not the HTTP attempt. The meter stores the key with the event. A later event with the same key is a duplicate and isn't counted again.

Choosing a good key

Same key, different payload

If a key comes back with a different amount or customer, something is wrong: a bug in key generation or a real change. Usage Lab keeps the original and returns 409 idempotency_conflict with original_event_id, so silent overwrites can't change a bill. An identical retry returns 200 with "duplicate": true and the original event id.

Timestamps and retries

If the meter stamps the time on arrival, a retry an hour later could fall in a different month. Set the timestamp yourself, once, before the first attempt. Usage Lab compares the timestamp only when you send one, so retries that omit it are still treated as duplicates.

Batches

Give each event in a batch its own idempotency_key, or send one Idempotency-Key header and Usage Lab derives <header>:0, <header>:1, … per position. Positional keys are only safe if you resend the exact same batch in the same order, so per-event keys are better.

How long keys are kept

A key only protects you while the meter remembers it. Usage Lab keeps keys indefinitely in v1, longer than its 30-day late window. For comparison, research.md notes Metronome dedupes transaction_id for 34 days (source) and m3ter dedupes measurement uid for 35 days (source).

Code: retry-safe pattern in Node · Reference: idempotency rules