Retries and idempotency
Both directions are built so that "send it again" is always safe.
Your calls to XQUARE: Idempotency-Key
Send an Idempotency-Key header on every POST. Use a value that identifies the action on
your side — a receipt number, a scan id, a UUID you store with the attempt.
- Same key and same body, within 24 hours → you get the first response back, with the header
Idempotent-Replayed: true; nothing happens twice. - Same key with a different body (or another endpoint) →
409 idempotency_key_reused; use a new key for a new action. - Same key while the first request is still running →
409 idempotency_request_in_progresswithRetry-After; retry with the same key. - No key on a
POST→400 idempotency_key_required. - Keys are scoped to the API key that sent them: another API key's identical
Idempotency-Keynever replays your response. - A
5xxanswer is not stored: retrying with the same key runs the request again. - Answers you can fix without changing the request are not stored either:
401,403,408,409,425,429, andcredits_exhausted/credits_suspended/no_payer/allowance_exhausted. Fix the key or plan, top up (or wait forRetry-After) and retry with the same key. Other4xx(400,404,422) are stored for 24 hours like a success.
Retry on network errors, timeouts, 429 (after Retry-After) and 5xx, with the same
key. Do not retry other 4xx — fix the request.
Voucher and sponsor redemptions are also protected inside the database: a retry with the same
Idempotency-Key that gets past the replay above still redeems (and charges) only once.
XQUARE's deliveries to you: retries
A delivery counts as delivered when your endpoint answers 2xx. Otherwise:
| Your answer | What XQUARE does |
|---|---|
2xx | Delivered. Done. |
timeout, connection error, 408, 429, 5xx | Retries on the schedule below |
410 Gone | Stops, and pauses the endpoint — you said it is gone |
any other 4xx, any 3xx | Stops at once (no retry) |
Retry schedule — the wait before each next attempt:
| After attempt | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
| Wait | 1 min | 5 min | 30 min | 2 h | 6 h | 12 h | 24 h |
Each wait has up to 10 % random jitter. The 8th failed attempt is final, about 44.6 hours
after the first. If you send Retry-After (in seconds) and it is shorter than the next
wait, XQUARE retries sooner; it never waits longer than the schedule.
A delivery that fails for good is marked failed. You can send it again from the Connections page in the vendor panel; a resend keeps the same delivery id. An endpoint that keeps failing is marked failing and then disabled, and the Space owner gets a notice (see Receive and verify webhooks).
Deduplicating on your side
Retries mean you can receive the same event more than once. Deduplicate on the event id
in the body (stable for that event), or on X-XQ-Delivery (stable across retries and resends of
one delivery). Store the ids you have processed for at least 3 days — longer than the full retry
window.
Order is not guaranteed: two events for the same voucher can arrive out of order after a
retry. Use created_at in the envelope, or fetch the current state from the API, when order
matters.