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
| Condition | Recommended behavior |
|---|---|
| Network timeout | Retry with the same Idempotency-Key for send-creating requests. |
| 429 rate_limited | Wait for Retry-After or until X-RateLimit-Reset. |
| 503 downstream unavailable | Use exponential backoff with jitter and retry later. |
| 400 invalid_request | Do not retry unchanged. Fix the request body or headers first. |
| 401/403 | Do 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.