Skip to content

Idempotency

Send an Idempotency-Key with every verification so network retries return the original result and are never billed twice.

View as Markdown

Networks fail. A request can time out after Speedmap has already run the check, and a naive retry would run (and bill) it again. An idempotency key makes retries safe: Speedmap remembers the first result for that key and returns it again.

Add the header to every POST to a product endpoint:

Idempotency-Key: 4f9d3c1e-8a2b-4c7d-9e0f-1a2b3c4d5e6f
curl -X POST http://localhost:4000/v1/verify/account \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "account_number": "0123456789",
  "bank_code": "058",
  "match": {
    "name": "Clarke Griffin"
  }
}'

Rules

  • Format. Any string of 8 to 128 characters. A UUID v4 is ideal. Anything else returns 400 invalid_idempotency_key.
  • Scope. Keys are scoped to your workspace and mode: the same key in sandbox and live refers to two different requests.
  • Same key, same body. You get the original verification back, with the same reference_id, and you are not charged again. If the original is still pending you get 202 and the pending verification.
  • Same key, different body. Rejected with 409 idempotency_key_reused. Generate a new key for every new check.
  • Failed attempts release the key. If the first attempt failed (for example 503 verification_unavailable during a provider outage), the key is released and the retry runs the check again. A failed attempt is never billed.
  • Where it applies. Product endpoints (POST /v1/verify/* and POST /v1/signals). Bulk jobs assign a key to every row for you.
409 Conflict
{
  "error": {
    "code": "idempotency_key_reused",
    "message": "Idempotency-Key was already used with a different request"
  }
}

A retry loop

Generate the key once per logical check, outside the retry loop, and reuse it on every attempt:

async function verifyWithRetry(path, body, attempts = 4) {
  const key = crypto.randomUUID(); // one key for every attempt
  for (let i = 0; i < attempts; i++) {
    const res = await fetch(`${process.env.SPEEDMAP_API_URL}${path}`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.SPEEDMAP_API_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": key,
      },
      body: JSON.stringify(body),
    }).catch(() => null); // network error: retry

    if (res && res.status < 500 && res.status !== 429) return res.json();
    const wait = Number(res?.headers.get("Retry-After")) || 2 ** i;
    await new Promise((r) => setTimeout(r, wait * 1000));
  }
  throw new Error("Speedmap unavailable after retries");
}

Questions? Email developers@speedmap.ng. This page is also available as Markdown.