Guides › Retry failed requests

Guide

Retry failed requests

Retry only when it is safe. Use stable idempotency keys for send-creating requests, respect rate-limit headers, and treat unknown responses as retryable until you can confirm the outcome.

When to retry

ConditionRecommended behavior
Network timeoutRetry with the same Idempotency-Key for send-creating requests.
429 rate_limitedWait for Retry-After or until X-RateLimit-Reset.
503 downstream unavailableUse exponential backoff with jitter and retry later.
400 invalid_requestDo not retry unchanged. Fix the request body or headers first.
401/403Do not retry unchanged. Fix credentials, workspace context, or permissions.

Backoff pattern

const retryableStatus = new Set([408, 429, 500, 502, 503, 504]);

async function sleep(ms) {
  await new Promise((resolve) => setTimeout(resolve, ms));
}

async function sendWithRetry(requestFactory) {
  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch(requestFactory());
    if (!retryableStatus.has(response.status)) return response;

    const retryAfter = Number(response.headers.get("Retry-After"));
    const waitMs = Number.isFinite(retryAfter)
      ? retryAfter * 1000
      : Math.min(8000, 500 * 2 ** attempt);
    await sleep(waitMs + Math.floor(Math.random() * 250));
  }
  throw new Error("request still failing after retries");
}

Webhook retries

Your webhook listener should return a 2xx status only after it has durably accepted the event. Use X-CTCT-Delivery-Id or Idempotency-Key to deduplicate retries.