Skip to content

idempotency_key_reused

HTTP 409 type: conflict param: Idempotency-Key
{
"error": {
"type": "conflict",
"code": "idempotency_key_reused",
"message": "Idempotency-Key 8f0b4e2a-6c31-4d9f-9a75-2b8e1c0d47f3 was already used for a different request body. Use a new key for a new sale, and the original key only for retries of the original sale.",
"param": "Idempotency-Key",
"upstream": null,
"doc_url": "https://docs.fiskhub.com/errors/idempotency_key_reused"
}
}

What happened

This Idempotency-Key was used within the last 24 hours with a different request body.

Bodies are compared by hash. Same key plus an identical body replays the stored response; same key plus a different body is this conflict. Nothing was processed — the request was refused outright rather than guessing which of the two sales you meant.

Should you retry?

Not as sent. Work out which case you are in first.

The fix

If this is a genuinely different sale

Use a new key. One key per sale, generated when the transaction begins.

The usual cause is a key derived from something that repeats — device_id + total, or a counter that resets. Two identical €4.50 sales at the same machine are different sales, and a derived key makes the second one look like a retry of the first. Derive keys from your own transaction id, or use a UUIDv4.

If this is meant to be a retry of the same sale

The body changed between attempts, and it must not. Common causes:

  • occurred_at recomputed as new Date() on each attempt. It must be the original sale moment, fixed when the sale happened, not the moment of the request.

  • The body re-serialised per attempt, producing a different key order and therefore a different hash. Serialise once and reuse the string:

    // Serialise ONCE, outside the retry loop.
    const body = JSON.stringify(sale.payload);
    for (let attempt = 1; attempt <= 3; attempt++) {
    const res = await fetch(url, {
    method: "POST",
    headers: { "Idempotency-Key": sale.idempotencyKey, ...headers },
    body, // the same bytes every time
    });
    // ...
    }
  • A field added or removed between attempts — a special_purpose note appended on the second try, a rounding applied only on the retry.

Fix whichever it is, then send again with the original key.

Checking whether the first attempt succeeded

The conflict tells you the key has been used, not what happened. To find out, list the invoices for that device around that time:

Terminal window
curl -sS -G "https://api.fiskhub.com/v1/invoices" \
-H "Authorization: Bearer $FISKHUB_API_KEY" \
--data-urlencode "device_id=6f1c0f9e-2b7a-4a56-9d3e-1f0b8a4c7d21" \
--data-urlencode "from=2026-08-27T10:00:00+02:00"

If the sale is there, it was fiscalized and no further action is needed.

Lifetime

Records live 24 hours from first use, scoped to your API key and mode. A key reused after that window is treated as new.

See idempotency.