Skip to main content
The POST endpoints that dial phones, /v1/calls and /v1/batch-calls, accept an Idempotency-Key header. If your system retries a request after a timeout or a network error, sending the same key guarantees the operation runs only once.

How it works

When we receive this request:
  1. We look in the cache for that key within your account.
  2. Hit with the same body and endpoint → we return the cached response from the first request. We do not fire the call again.
  3. Hit with a different body, or the same key already used on another endpoint → we answer 409 idempotency_conflict. That is a bug on your side: you are reusing a key for different work.
  4. Miss → we process the request normally. If it succeeds, we cache (key, body, response) for 24h.
On /v1/batch-calls the key is claimed before the batch is sent, so two retries that arrive at the same time cannot launch two batches: the second one gets 409 idempotency_in_progress until the first finishes, and then the cached response. If the first request fails with a 4xx (invalid body, not enough budget), the key is released and you can fix the request and retry with the same key.

When to use it

Firing calls from a workflow

Your n8n or GHL can retry on a timeout. Without idempotency, every retry fires a new call.

Queued messages

If your queue delivers “at-least-once” (SQS, RabbitMQ), the same message can arrive twice.

Webhooks from your CRM

Your CRM may retry failed webhooks, so use the CRM’s own ID as the Idempotency-Key.

Any async code

Wherever retries happen, send the key.

Key format

  • 1 to 255 characters.
  • Printable ASCII only (! to ~).
  • Recommended: UUID v4 (crypto.randomUUID()) or an identifier from your own system (for example lead-{lead_id}-{deal_id}).

Cache TTL

Keys expire 24 hours after the first successful request. After that, the same key can be used for a new, different operation. If you need duplicate protection beyond 24h, use stable identifiers on your side (for example a unique webhook_id in your CRM) and keep your own webhook_id → call_id mapping.

Isolation per customer

Keys are scoped to your account. If you use Idempotency-Key: 12345 and another Ryvo customer uses the same string, there is no collision: each account has its own namespace. Within your account, all your API keys share that namespace: the same key sent from two different API keys of the same account is the same operation.

Example: integration with backoff

Because we always send the same idempotencyKey, even if the first attempt fired the call successfully but the response was lost on the network, the retry returns that same cached response and does NOT fire a second call.

Common errors