Ir al contenido

Idempotency

Esta página aún no está disponible en tu idioma.

Network failures happen. A request may time out before you receive a response, leaving you unsure whether the server processed it. Resending the request without protection could create a duplicate ticket, charge a customer twice, or fire a notification a second time.

The BenchKey API supports idempotency keys on all mutating requests (POST, PUT, PATCH, DELETE). Send the same key on a retry and you get back the original response with no side effects repeated.

Add an Idempotency-Key header containing a unique string to your request:

POST /api/v1/tickets
Authorization: Bearer bk_live_acme_xxxxxxxxxxxx
BenchKey-Version: 2026-06-13
Content-Type: application/json
Idempotency-Key: idem_01J8X4abc123def456
  • The server records the key + response when the request first completes. The fingerprint includes the HTTP method, path, and query string, a retry must be identical on all three to match.
  • Completed responses are normally retained for 24 hours. An identical retry in that window replays the stored response without repeating the write.
  • Some operations also keep a durable command or business-event identity. Do not assume a key becomes reusable after 24 hours; use a new key for a new action.
  • An unfinished request can remain reserved while its outcome is reconciled. Retry the identical request with its original key; do not create another submission to bypass a pending result.

The replay is returned with the same HTTP status code and body as the original response, plus the header:

Idempotent-Replayed: true

Use a cryptographically random string. A UUID v4 is the simplest choice:

curl

Terminal window
KEY=$(python3 -c "import uuid; print('idem_' + str(uuid.uuid4()).replace('-',''))")
curl -X POST https://app.benchkey.com/api/v1/tickets \
-H "Authorization: Bearer $BENCHKEY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" \
-d '{
"customer_email": "jane@example.com",
"customer_name": "Jane Smith",
"device_name": "iPhone 14",
"service_name": "Screen replacement",
"notes": "Cracked screen"
}'

Node.js

import crypto from "node:crypto";
const idempotencyKey = "idem_" + crypto.randomBytes(16).toString("hex");
const res = await fetch("https://app.benchkey.com/api/v1/tickets", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BENCHKEY_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify({
customer_email: "jane@example.com",
customer_name: "Jane Smith",
device_name: "iPhone 14",
service_name: "Screen replacement",
notes: "Cracked screen",
}),
});
const ticket = await res.json();
console.log(ticket);

Generate and persist the key once with the operation before sending, then pass that saved key to the helper on every attempt, including after a process restart. Keep the request body unchanged. Completed responses are normally replayable for 24 hours; beyond that window, check the saved result and resource state before resubmitting.

async function postWithIdempotency(path, body, key) {
if (typeof key !== "string" || !key) {
throw new Error("Pass the operation's persisted idempotency key");
}
const url = `https://app.benchkey.com/api/v1${path}`;
const options = {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BENCHKEY_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": key, // same key for every attempt
},
body: JSON.stringify(body),
};
let backoff = 1000;
for (let attempt = 0; attempt < 5; attempt++) {
let res;
try {
res = await fetch(url, options);
} catch (err) {
if (attempt === 4) throw err;
await new Promise((r) => setTimeout(r, backoff + Math.random() * 200));
backoff = Math.min(backoff * 2, 30_000);
continue;
}
if (res.ok) return res.json();
const payload = await res.json().catch(() => null);
const failure = new Error(payload?.error
? `[${payload.error.code}] ${payload.error.message}`
: `HTTP ${res.status}`);
const retryable = res.status === 408 || res.status === 429 || res.status >= 500;
if (!retryable || attempt === 4) throw failure;
const retryAfter = res.headers.get("Retry-After")?.trim();
const retryDate = Date.parse(retryAfter ?? "");
const wait = /^\d+$/.test(retryAfter ?? "")
? Number(retryAfter) * 1000
: Number.isFinite(retryDate) ? Math.max(0, retryDate - Date.now()) : backoff;
await new Promise((r) => setTimeout(r, wait + Math.random() * 200));
backoff = Math.min(backoff * 2, 30_000);
}
}

If you reuse a key with a different request body or URL, the API returns 409 Conflict with code idempotency_key_reuse:

{
"error": {
"type": "conflict_error",
"code": "idempotency_key_reuse",
"message": "An idempotency key was reused with a different request body.",
"request_id": "req_01J8X4zzzzzz"
}
}

This is always a bug in the caller. Generate a new key for each logically distinct write.

For 409 Conflict with code idempotency_in_progress, wait and retry with the same key and unchanged body. If the API returns idempotency_outcome_unknown, stop automatic write retries and reconcile the saved result before resubmitting. Do not use a fresh key to bypass the conflict.

DetailValue
Applies toPOST, PUT, PATCH, DELETE
Key TTL24 hours
Key min length8 characters
Key max length255 characters
Header nameIdempotency-Key

The general idempotency namespace includes the tenant and key environment label. Replay still checks the caller’s current authority, and some commands, including webhook-secret rotation, bind the key to the acting principal. An environment label does not isolate workspace data.

An Idempotency-Key is required for ticket creation, payment recording, invoice and payment refunds, customer consent writes, and webhook-secret rotation. Payment-recording keys allow 8 to 220 characters; the other listed operations allow up to 255. Use a UUID or another durable identifier and retain it with your submission.

Use them on every write that has side effects you can’t easily reverse:

  • Creating tickets, invoices, or estimates
  • Recording a payment
  • Sending an SMS or email notification
  • Any POST that charges money or modifies inventory

Use the documented method for each resource: ticket edits use PATCH /tickets/:id. Keep the same key and body when retrying the same edit.

  • Persist the key before sending: store it alongside your pending operation so you can recover it after a crash and retry with the same key.
  • Use meaningful prefixes: e.g., idem_ticket_ or idem_pmt_ to aid debugging in logs.
  • Never reuse across unrelated operations: one key, one logical operation.
  • Combine with exponential backoff: retries on 429 or 5xx should always carry the original idempotency key.
Estado del sistema