Skip to main content
Retries are how networks are survived, and a retry that is not idempotent double-counts a purchase or creates a second decision. Both write endpoints give you a key that makes a retry safe.

Events: id

Give each event a stable id. A repeated id is deduplicated and answered as a success with duplicate: true; in a batch, the item is { "status": "ok", "duplicate": true } and the envelope’s duplicates counts them. Ids are unique per organization and environment. Events without an id are delivered at least once. A good id is derived from something stable on your side, so the same fact always produces the same id: <entity id>_<event name>, such as ord_8f3a_purchased or inv_2201_refunded. The server SDK mints sdk-js:<uuid> once per track() call and keeps it across every retry.

Decide: idempotency_key

A decision is a write: it records which arm the user saw, and a second one ends the first’s observation window. So a retried decide must not create a second decision. Send an idempotency_key generated before the first attempt and reused on every retry:
  • An opaque string of 1 to 255 characters; a UUID generated per logical request is the recommended shape. It may not contain U+0000 or an unpaired surrogate.
  • Keys are scoped to the organization and environment: a test retry never replays a live decision.
  • In a batch, the key is per item, and the envelope’s replayed counts the replayed items.
“Same request” is judged on the request’s content, not its formatting. Failures are not stored: a 400 under a key leaves the key unclaimed.

Retry matrix

Cap retries at a handful with exponential backoff. The server SDK makes five attempts per event and never retries decide.

Ambiguous failures

A 502 or a dropped connection after the request left you tells you nothing about whether the origin committed. The response’s message says so and names the safe retry: the same idempotency_key for decide, the same id for events. Both make the retry safe either way: if the original committed, you get it back; if it did not, the retry is the first attempt.