Reliability
Idempotency
Retry POST requests safely. Send an Idempotency-Key header and KrokoPay performs the operation once, answering every repeat with the original response.
On this page
Networks fail in the worst place: after KrokoPay created the charge, but before your server read the response. Without protection, the natural fix, retrying, creates a second charge. An idempotency key tells KrokoPay that several requests are the same operation, so it runs once no matter how many times you send it.
The Idempotency-Key header
Add Idempotency-Key to any POST request. The first request with a given key is processed normally; later requests with the same key and the same body get the stored result.
curl -X POST https://api.krokopay.app/v1/charges \ -H "Authorization: Bearer kp_test_sk_4f9a2c7e1b8d4a6f9c3e5d7b" \ -H "Idempotency-Key: 6f1c2b8e-3d4a-4e5f-9a7b-1c2d3e4f5a6b" \ -H "Content-Type: application/json" \ -d '{ "amount": 49700, "currency": "BRL", "description": "Curso Identidade Visual do Zero", "customer_id": "cus_8Hq2LmZr4TxN1a", "methods": [ "pix", "card" ] }'| Method | Idempotency-Key |
|---|---|
POST | Recommended on every request that creates or changes something: charges, refunds, customers, payment links, subscriptions, pause and resume. |
GET | Not needed. Reading has no side effects and can always be retried. |
DELETE | Not needed. Deleting the same object twice leaves it in the same state. |
Generating keys
- Use a UUID v4 (
crypto.randomUUID()), or a value derived from your own record that is unique per operation, such asorder_1042orrefund_1042_1. - Generate the key once per operation and store it with the record. Every retry of that operation must reuse it.
- Use a new key for a new operation: a second refund of the same charge is a different operation and needs its own key.
- Never reuse a key across unrelated requests, and never put personal data (emails, documents) in it.
- The maximum key length is to be defined. A UUID (36 characters) is a safe choice.
// Generate the key once per operation and store it with the order,// so every retry of that operation reuses it.const idempotencyKey = crypto.randomUUID(); // e.g. "6f1c2b8e-3d4a-4e5f-9a7b-1c2d3e4f5a6b"await db.orders.update(order.id, { idempotencyKey });What happens on replay
Step 1: First request
Processed normally. The status code and body are stored with the key.
Step 2: Same key, same body
Not processed again. You receive the stored status and body, identical to the first response, even if that response was an error such as a validation
422.Step 3: Same key, different body
Rejected. See Conflicts.
Success: Replays are safe to show
As proposed, keys are scoped to your account and to the environment of the API key used, so test requests never collide with production ones. What happens when a second request arrives while the first is still being processed is to be defined; until then, avoid sending the same key concurrently.
Conflicts
Reusing a key with a different body (another amount, another customer) usually means a bug: two operations share a key. KrokoPay refuses the request instead of guessing which one you meant.
curl -X POST https://api.krokopay.app/v1/charges \ -H "Authorization: Bearer kp_test_sk_4f9a2c7e1b8d4a6f9c3e5d7b" \ -H "Idempotency-Key: 6f1c2b8e-3d4a-4e5f-9a7b-1c2d3e4f5a6b" \ -H "Content-Type: application/json" \ -d '{ "amount": 59700, "currency": "BRL", "description": "Curso Identidade Visual do Zero", "customer_id": "cus_8Hq2LmZr4TxN1a", "methods": [ "pix", "card" ] }'HTTP/1.1 409 ConflictContent-Type: application/json{ "error": { "type": "idempotency_error", "code": "idempotency_key_reused", "message": "This Idempotency-Key was already used with a different request body." }}Information: Proposed
409 status and the idempotency_key_reused code are a proposal and may change. The general error format is described in Errors.How long keys are kept
Stored results are kept for a limited time, after which a key can be used again as if new. That retention period is to be defined. Do not rely on old keys to deduplicate: retry promptly, and for long-running workflows check the state of your own record first (for example, whether the order already has a charge id).
A retry loop with fetch
Retry only when the outcome is unknown or temporary: network errors, timeouts and 5xx responses. A 4xx means the request itself must change, so retrying it unchanged returns the same answer.
const API_URL = "https://api.krokopay.app/v1";const SECRET_KEY = process.env.KROKOPAY_SECRET_KEY ?? ""; // kp_test_sk_…const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));/** * POSTs with the same Idempotency-Key on every attempt. Retries network * failures and 5xx answers only; any 4xx is returned to the caller as is. */export async function postWithRetry( path: string, body: Record<string, unknown>, idempotencyKey: string, maxAttempts = 3,): Promise<Response> { for (let attempt = 1; ; attempt++) { try { const response = await fetch(`${API_URL}${path}`, { method: "POST", headers: { Authorization: `Bearer ${SECRET_KEY}`, "Idempotency-Key": idempotencyKey, "Content-Type": "application/json", }, body: JSON.stringify(body), // identical on every attempt }); if (response.status < 500 || attempt >= maxAttempts) return response; } catch (error) { // Timeout or dropped connection: the charge may or may not exist yet. if (attempt >= maxAttempts) throw error; } // Exponential backoff with jitter before the next attempt. await sleep(2 ** attempt * 250 + Math.random() * 250); }}// Usage: the key comes from the order, not from the attempt.const response = await postWithRetry( "/charges", { amount: 49700, currency: "BRL", description: "Order 1042", methods: ["pix", "card"] }, order.idempotencyKey,);const charge = await response.json();| Result | Retry? |
|---|---|
| Network error or timeout | Yes, with the same key. |
5xx | Yes, with the same key and a growing delay. |
409 conflict | No. Fix the key or the body. |
Other 4xx (401, 404, 422) | No. Fix the request; see Errors. |
2xx | No. Done. |
Related
- Creating charges: where idempotency matters most.
- Webhooks: deduplicating events on your side.
- Errors: status codes and the error format.