Idempotency
Safe retries with Idempotency-Key — how ZapAPI guarantees that retried POST requests never double-charge a customer.
Network failures happen. A request can time out after the server has processed it, leaving you unsure whether the operation succeeded. ZapAPI solves this with idempotency keys: send the same key on a retry and the API returns the original response instead of creating a duplicate.
How it works
Every POST endpoint accepts an Idempotency-Key header:
POST /api/v1/invoices
Idempotency-Key: order-12345-create- The key is stored alongside the request fingerprint (method, path, body hash).
- A retry with the same key and same body returns the original response (including the original status code), without re-executing the operation.
- A retry with the same key but a different body returns
409 Conflict(idempotency_conflict). - Keys are scoped to your project and expire after 24 hours.
Using idempotency keys in the SDK
The TypeScript SDK accepts an idempotency key as the second argument to every create-style method:
const invoice = await zap.invoices.create(
{
amount: 21000,
description: 'Pro plan',
},
`order-${orderId}`, // safe retries — never double-charge
);If the request times out, retry with the same key — the API returns the original invoice, not a duplicate.
Picking a good key
A good idempotency key is:
- Unique per logical operation — e.g.,
order-12345-create,subscription-6789-renew-2026-10. - Stable across retries — derive it from your internal id, not from a random value you regenerate on each attempt.
- Namespaced — include the operation so the same logical id can be reused for different operations.
UUIDs are fine if you generate them once per operation and persist them.
What idempotency does NOT do
- It is not a transaction. If a request partially succeeds and then fails, the idempotency key only protects the API operation, not your downstream side effects.
- It does not dedupe across keys. Two different keys always create two resources.
- It does not replace webhooks. Idempotency makes your retries safe; webhooks are how you learn about asynchronous settlement.
Conflicts and how to handle them
A 409 Conflict (idempotency_conflict) means you reused a key with a
different body. This is almost always a bug — you likely generated the key
from insufficiently-unique data. The correct response is to fetch the original
resource by id (returned in the conflict response) rather than retrying.
try {
await zap.invoices.create(params, key);
} catch (err) {
if (err instanceof ZapAPIError && err.isConflict) {
// Fetch the original invoice created with this key
const original = await zap.invoices.get(err.payload.data.id);
return original;
}
throw err;
}Which endpoints support it
All POST endpoints that create or mutate state accept Idempotency-Key,
including:
POST /invoicesPOST /payment-linksPOST /refundsPOST /webhooksPOST /creator-plansPOST /customers
GET, PATCH and DELETE are naturally idempotent and do not require a key.