Skip to content

Idempotency

Make requests that create or move money safe to retry with the Idempotency-Key header of the Baiyar API.


A request that times out may or may not have happened. To retry without risking a second payment, send an Idempotency-Key header, 1 to 255 characters (a UUID or an order number with an attempt counter works well). It is required on:

  • POST /v1/payments
  • POST /v1/payment-links
  • POST /v1/payment-links/{id}/cancel
  • POST /v1/payments/{id}/cancel
  • POST /v1/webhook-deliveries/{id}/resend
  • POST /v1/invoices, and POST /v1/invoices/{id}/issue, /void and /resend (so a retry never mails a customer twice)
POST /v1/payments
Idempotency-Key: order-1001-attempt-1
You send What happens
A key for the first time The request runs, and its answer is stored for 24 hours
The same key and the same body again You get the stored answer, with the header Idempotent-Replayed: true. Nothing runs twice
The same key with another body or another route 409 idempotency_key_reused
The same key while the first request is still running 409 idempotency_in_progress with Retry-After: send the same request again after that many seconds
A key whose first request failed before it had any effect The key is not kept: you can send it again with the same key

Make the key before the first try, keep it with your order, and use the same key and the same body on every retry:

import { randomUUID } from "node:crypto";
// made once, kept with the order in your database
const idempotencyKey = `order-${order.id}-${randomUUID()}`;
async function createPayment(body, attempts = 3) {
for (let attempt = 1; ; attempt++) {
try {
const response = await fetch("https://api.baiyar.com/v1/payments", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BAIYAR_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify(body),
});
if (response.status < 500 || attempt === attempts) return response;
} catch (error) {
if (attempt === attempts) throw error; // a timeout or a dropped connection
}
await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 500));
}
}

If the first try did reach us, the second returns the same payment in data with Idempotent-Replayed: true, and no second payment is made. Its meta.request_id is the id of the request that replays it, not of the first one.

  • Make the key once per intended operation and reuse it only for retries of that operation. A new order, or a payer who asks to pay again, is a new key.
  • Keep the key together with the order in your database before you send the request, so a crash does not lose it.
  • Reading (GET) never needs a key.
  • A key is remembered for 24 hours. After that, the same key starts a new request.