Concepts

Idempotency

Networks fail. When a request times out you cannot know whether it was carried out — and a retried call request must never ring someone twice. An Idempotency-Key makes the retry safe.

How it works

Send a unique string in the Idempotency-Key header — a UUID is ideal. The first request with that key does the work and Voximate stores its answer for 24 hours. Any later request with the same key gets that same stored answer back, with the header Idempotent-Replayed: true, and nothing is done again.

Which endpoints

The endpoints that create something accept the header:

GET and DELETE are already safe to repeat. PATCH sets fields to the values you send, so repeating it gives the same result.

Example

Create the key once, before the first attempt, and reuse it for every retry of that same operation.

const key = crypto.randomUUID(); // one key per operation, not per attempt

async function startCall(attempt = 0) {
  try {
    const res = await fetch('https://api.voximate.no/public/v1/calls', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.VOXIMATE_API_KEY}`,
        'Content-Type': 'application/json',
        'Idempotency-Key': key,
      },
      body: JSON.stringify({ agent_id: '7c3f1e90-5a44-4b21-9c07-2ed5b8f41a66', to: '+4712345678' }),
    });
    if ((res.status === 429 || res.status >= 500) && attempt < 4) throw new Error(`HTTP ${res.status}`);
    return await res.json();
  } catch (err) {
    if (attempt >= 4) throw err;
    await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));
    return startCall(attempt + 1); // same key: at most one call is placed
  }
}

Rules

SituationWhat you get back
Same key, same request, first one finishedThe first answer again, with Idempotent-Replayed: true.
Same key, different request body or endpoint409 idempotency_error, code idempotency_key_reused.
Same key while the first request is still running409 idempotency_error, code idempotency_request_in_progress. Retry shortly.
The first request was refused with a 429, or for a reason that can change (call_rejected because a trial line is busy)Nothing is stored. Wait (Retry-After says how long) and retry with the same key.
The first request failed with a 5xxNothing is stored, so a retry with the same key runs again.
POST /calls answered 503 call_outcome_unknownStored. The call may be ringing, so the same key never dials again. Look for the call with GET /calls; to try again, use a new key.
More than 24 hours laterThe key has expired and the request runs as new.
  • Keys are per workspace and at most 255 characters.
  • Validation errors (4xx other than 429) are stored like successes, so fix the request and use a new key.
  • A replayed POST /webhooks answer has secret: null. The signing secret is kept only in the vault, never with the stored answer; if you lost it, rotate it.
Derive the key from your own data when you can — for example call-{crm_contact_id}-{campaign_id} — so even a restarted job cannot call the same person twice for the same reason.