Idempotency and retries
A request can time out after the link is created but before the response arrives. The Idempotency-Key header makes it safe to send the request again.
The rule
Section titled “The rule”POST /personal-links needs an Idempotency-Key header of up to 255 characters. Use a value that is unique to what the request does, and use the same value for every retry of that request.
For a personal link, build the key from the occasion and the player, such as birthday-2026:player-123. If a journey runs twice for the same player, it sends the same key, and the player gets one link.
POST /reward-store-orders also needs an Idempotency-Key header. Build it from your own order, such as shop-order-58213. Then a retry never places a second order or takes the player’s coins twice. For more information, see Rewards stores.
POST /v1/personal-linksAuthorization: Bearer lxr_live_…Idempotency-Key: birthday-2026:player-123Content-Type: application/jsonThe following table shows how the API responds:
| Request | Response |
|---|---|
| New key | The request runs. A 2xx response is stored. |
Same key, same request, after a 2xx |
The stored response is replayed with Idempotent-Replayed: true. Nothing runs twice. |
Same key, different request, after a 2xx |
422 idempotency_key_reused. Nothing runs. |
| Same key, same request, while the first is still running | 409 idempotency_request_in_progress. Retry in a few seconds. |
| No key | 400 idempotency_key_required. Nothing runs. |
| Key longer than 255 characters | 400 idempotency_key_invalid. Nothing runs. |
The following rules also apply:
- Same request means the same method, path, query, and JSON body content. The order of keys and the whitespace in the body don’t matter. A changed value, an added field, or a removed field makes it a different request.
- Scope. An idempotency key belongs to one API key. Two API keys can use the same value.
- Retention. Idempotency keys are kept for 7 days. After that, the same idempotency key runs as a new request. A personal link that you send again with it is a second link.
- Stored responses. Only
2xxresponses are stored. If a request returns4xxor5xx, or never arrives, a retry with the same key runs again. For example, if a link is refused because the campaign isn’t active, you can send it again with the same key after the cause is fixed.
Calls that need no key
Section titled “Calls that need no key”The following calls don’t need an Idempotency-Key header:
| Call | Idempotency-Key |
|---|---|
POST /personal-links/{linkId}:cancel |
Optional. You can repeat the call safely without a key: canceling a link that is already CANCELLED returns 200 and changes nothing. If you send a key, the rules on this page apply, and a retry with it replays the first response. |
POST /webhooks:test |
Ignored. Every call sends a real test event, so a retry after you fix your receiver shows its new response. |
Retry after a timeout
Section titled “Retry after a timeout”If a request to create a link times out, do the following:
- Send the request again with the same key and the same body. You can rebuild the JSON if the values are the same.
- If the first request created the link, the retry returns the same
201response and link, withIdempotent-Replayed: true. No second link is created. - If the first request didn’t create the link, the retry creates it.
What to retry
Section titled “What to retry”Use the following table to decide whether to retry:
| Response | Retry |
|---|---|
| Timeout or connection error | Yes, with the same key and body. |
429 rate_limited |
Yes, after the number of seconds in the Retry-After header. |
409 idempotency_request_in_progress |
Yes, with the same key and body, after a few seconds. |
5xx |
Yes, with the same key and body. Wait longer between retries: 1 second, 2 seconds, 4 seconds, up to a minute. |
Other 4xx |
Not as it is. Fix the request or its cause first. You can use the same key again, because a refusal isn’t stored. |
GET requests need no idempotency key, and you can always retry them safely.

