Developers / Idempotency

Idempotency

Retry a payment or a new webhook endpoint without ever doing it twice. Send an Idempotency-Key and reuse it on every retry.

Updated 4 November 2026API v1.0.1

Which requests need one#

A POST that creates a record or moves money needs an Idempotency-Key. Today that is:

The other writes are safe to repeat as they are:

  • Suspending a subscriber who is already suspended answers done and changes nothing. The same goes for reactivating one who is already active.
  • Changing to the plan a subscriber is already on answers 422 same_plan and changes nothing.
  • Changing an endpoint sets the same values again. Deleting one twice answers 404 the second time.

How to send one#

  • Any unique string of 1 to 255 printable ASCII characters, without spaces. A UUID is ideal.
  • Make it once for each piece of work, such as one payment, and save it with that work before the first try. Then a retry, even after your own program crashed, reuses it.
  • Keys are kept per API key. Two of your keys can use the same string without clashing.
Shell
curl https://coresyncapi.avitechcloud.com/api/v1/payments \
  -H "X-API-Key: $CORESYNC_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 4d1c9f0e-7a2b-4f43-9a51-2f0c7c1d8e11" \
  -d '{
    "subscriber_id": "3f2a6c1e-8b7d-4c1a-9e2f-1a2b3c4d5e6f",
    "amount": "1500.00",
    "provider": "MPESA",
    "reference": "SAMPLE7Q2Z"
  }'

What happens on a repeat#

“The same request” means the same operation with the same JSON body.

You sendYou get
The same key and request, after the first finishedThe first answer again, same status and body, with Idempotent-Replayed: true. Nothing runs twice.
The same key and request, while the first is still running409 idempotency_in_progress. Retry in a few seconds.
The same key with a different request422 idempotency_key_reused. Use a new key for new work.
The same key after the first failed with an errorThe request runs again for real. Errors are not kept.
The same key after 24 hoursThe key has been forgotten. The request runs again.
A replayed answer
HTTP/1.1 201
Idempotent-Replayed: true
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 117
Content-Type: application/json

{ "id": "1c2d3e4f-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "status": "COMPLETED", "applied": true, … }

Payments have a second guard#

A payment's provider and reference are only ever recorded once for your ISP, whatever the Idempotency-Key.

  • The same receipt for the same subscriber answers 200 with the payment already recorded. Nothing changes.
  • The same receipt for a different subscriber answers 409 duplicate_reference.
  • With no reference, CoreSync makes one from the Idempotency-Key. Then the key is your only guard, so pass the real receipt when you have it.
  • 422 payment_not_applied means the payment was saved but not applied. Retrying with the same key tries to apply it again.
// Retry a payment safely: same body, same Idempotency-Key, every time.
async function recordPaymentSafely(body, idempotencyKey) {
  for (let attempt = 1; ; attempt++) {
    try {
      return await api("/payments", {
        method: "POST",
        headers: { "Idempotency-Key": idempotencyKey },
        body,
      });
    } catch (err) {
      const retry =
        err.status === undefined || // the network failed: we don't know if it arrived
        err.status >= 500 ||
        err.status === 429 ||
        err.problem?.code === "idempotency_in_progress" ||
        err.problem?.code === "payment_not_applied";
      if (!retry || attempt === 5) throw err;
      await new Promise((r) => setTimeout(r, (err.retryAfter || attempt * 2) * 1000));
    }
  }
}
NextRate limits →

Stuck, or missing something? Tell us what you're building.

hello@coresyncisp.com