{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-docs/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["tabs","tab"]},"type":"markdown"},"seo":{"title":"Reefpay Docs","description":"Reefpay is the groundbreaking payment gateway that lets you integrate with the future of payments.","llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"tutorial-retry-with-exponential-backoff","__idx":0},"children":["Tutorial: Retry with exponential backoff"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"why-you-need-retries","__idx":1},"children":["Why you need retries"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Network timeouts, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["5xx"]}," server errors, and rate limits (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["429"]},") are normal in distributed systems. Without retries, a single transient failure can cause a lost payment or a broken checkout flow."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"the-algorithm","__idx":2},"children":["The algorithm"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"text","header":{"controls":{"copy":{}}},"source":"delay = min(base × 2^attempt + random_jitter, max_delay)\n","lang":"text"},"children":[]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Parameter"},"children":["Parameter"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Recommended value"},"children":["Recommended value"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Purpose"},"children":["Purpose"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["base"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["1 second"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Starting delay"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["max_delay"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["30 seconds"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Cap so you don't wait too long"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["max_retries"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["3"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Limit total attempts"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["jitter"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["0–1 second random"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Prevent thundering herd"]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"visual-flow","__idx":3},"children":["Visual flow"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"img","attributes":{"src":"https://files.modern-mermaid.live/images/1776277577094-mermaid-diagram-1776277577070.png","alt":"image"},"children":[]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"when-to-retry","__idx":4},"children":["When to retry"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Response"},"children":["Response"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Retry?"},"children":["Retry?"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Reason"},"children":["Reason"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["200"]}," with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["response_status: approved"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["No"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Success"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["200"]}," with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["response_status: declined"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["No"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Issuer refused — ask for different card"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["200"]}," with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["response_status: error"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Yes"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Gateway/processor issue"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["400"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["401"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["404"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["422"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["No"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Client error — fix the request"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["429"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Yes"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Rate limited"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["500"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["502"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["503"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["504"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Yes"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Server error — transient"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Timeout / connection error"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Yes"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Network issue"]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"complete-implementation","__idx":5},"children":["Complete implementation"]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"import time, random, requests, os, logging\n\nlogger = logging.getLogger(\"paradise\")\n\nAPI_KEY = os.environ[\"PARADISE_API_KEY\"]\nBASE = os.environ.get(\"PARADISE_BASE_URL\", \"https://api.dev.paradisegateway.net\")\n\ndef send_with_retry(\n    method: str,\n    path: str,\n    payload: dict | None = None,\n    max_retries: int = 3,\n    base_delay: float = 1.0,\n    max_delay: float = 30.0,\n) -> dict:\n    url = f\"{BASE}{path}\"\n    headers = {\"Content-Type\": \"application/json\", \"APIKEY\": API_KEY}\n\n    for attempt in range(max_retries + 1):\n        try:\n            resp = requests.request(\n                method, url, headers=headers, json=payload, timeout=30\n            )\n            body = resp.json()\n\n            if resp.status_code == 200 and body.get(\"response_status\") != \"error\":\n                return body\n\n            if 400 <= resp.status_code < 500 and resp.status_code != 429:\n                logger.error(\n                    \"Non-retryable error %s: %s (correlation_id=%s)\",\n                    resp.status_code, body, body.get(\"correlation_id\"),\n                )\n                return body\n\n        except (requests.exceptions.Timeout, requests.exceptions.ConnectionError) as e:\n            body = {\"error\": str(e)}\n\n        if attempt == max_retries:\n            logger.error(\"Max retries exhausted for %s %s\", method, path)\n            return body\n\n        delay = min(base_delay * (2 ** attempt) + random.uniform(0, 1), max_delay)\n        logger.warning(\n            \"Attempt %d/%d failed, retrying in %.1fs (correlation_id=%s)\",\n            attempt + 1, max_retries, delay, body.get(\"correlation_id\", \"n/a\"),\n        )\n        time.sleep(delay)\n\n    return body\n","lang":"python"},"children":[]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"JavaScript","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"const API_KEY = process.env.PARADISE_API_KEY;\nconst BASE = process.env.PARADISE_BASE_URL || \"https://api.dev.paradisegateway.net\";\n\nasync function sendWithRetry(\n  method,\n  path,\n  payload = null,\n  { maxRetries = 3, baseDelay = 1000, maxDelay = 30000 } = {}\n) {\n  const url = `${BASE}${path}`;\n  let lastResult;\n\n  for (let attempt = 0; attempt <= maxRetries; attempt++) {\n    try {\n      const controller = new AbortController();\n      const timer = setTimeout(() => controller.abort(), 30000);\n\n      const resp = await fetch(url, {\n        method,\n        headers: { \"Content-Type\": \"application/json\", \"APIKEY\": API_KEY },\n        body: payload ? JSON.stringify(payload) : undefined,\n        signal: controller.signal,\n      });\n      clearTimeout(timer);\n      lastResult = await resp.json();\n\n      if (resp.ok && lastResult.response_status !== \"error\") return lastResult;\n      if (resp.status >= 400 && resp.status < 500 && resp.status !== 429) return lastResult;\n    } catch (err) {\n      lastResult = { error: err.message };\n    }\n\n    if (attempt === maxRetries) break;\n\n    const delay = Math.min(baseDelay * 2 ** attempt + Math.random() * 1000, maxDelay);\n    console.warn(\n      `Attempt ${attempt + 1}/${maxRetries} failed, retrying in ${(delay / 1000).toFixed(1)}s`\n    );\n    await new Promise((r) => setTimeout(r, delay));\n  }\n\n  console.error(`Max retries exhausted for ${method} ${path}`);\n  return lastResult;\n}\n","lang":"javascript"},"children":[]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Go","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"go","header":{"controls":{"copy":{}}},"source":"package gateway\n\nimport (\n  \"bytes\"\n  \"encoding/json\"\n  \"fmt\"\n  \"io\"\n  \"math\"\n  \"math/rand\"\n  \"net/http\"\n  \"os\"\n  \"time\"\n)\n\nfunc SendWithRetry(method, path string, payload any) (map[string]any, error) {\n  apiKey := os.Getenv(\"PARADISE_API_KEY\")\n  base := os.Getenv(\"PARADISE_BASE_URL\")\n  if base == \"\" {\n    base = \"https://api.dev.paradisegateway.net\"\n  }\n  maxRetries, baseDelay, maxDelay := 3, 1.0, 30.0\n  var lastResult map[string]any\n\n  for attempt := 0; attempt <= maxRetries; attempt++ {\n    var bodyReader io.Reader\n    if payload != nil {\n      b, _ := json.Marshal(payload)\n      bodyReader = bytes.NewReader(b)\n    }\n    req, _ := http.NewRequest(method, base+path, bodyReader)\n    req.Header.Set(\"Content-Type\", \"application/json\")\n    req.Header.Set(\"APIKEY\", apiKey)\n\n    client := &http.Client{Timeout: 30 * time.Second}\n    resp, err := client.Do(req)\n    if err == nil {\n      defer resp.Body.Close()\n      b, _ := io.ReadAll(resp.Body)\n      json.Unmarshal(b, &lastResult)\n\n      status, _ := lastResult[\"response_status\"].(string)\n      if resp.StatusCode == 200 && status != \"error\" {\n        return lastResult, nil\n      }\n      if resp.StatusCode >= 400 && resp.StatusCode < 500 && resp.StatusCode != 429 {\n        return lastResult, fmt.Errorf(\"non-retryable: %d\", resp.StatusCode)\n      }\n    }\n\n    if attempt == maxRetries {\n      break\n    }\n    delay := math.Min(baseDelay*math.Pow(2, float64(attempt))+rand.Float64(), maxDelay)\n    time.Sleep(time.Duration(delay * float64(time.Second)))\n  }\n  return lastResult, fmt.Errorf(\"max retries exhausted\")\n}\n","lang":"go"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"usage","__idx":6},"children":["Usage"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"result = send_with_retry(\"POST\", \"/v1/transactions\", {\n    \"type\": \"sale\",\n    \"amount\": 1999,\n    \"payment_method\": {\n        \"type\": \"card\",\n        \"card\": {\n            \"pan\": \"4111111111111111\",\n            \"expiry_month\": \"12\",\n            \"expiry_year\": \"2027\",\n            \"cvv\": \"123\",\n            \"cardholder_name\": \"John Doe\",\n        },\n    },\n})\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"testing-retries-in-sandbox","__idx":7},"children":["Testing retries in sandbox"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Simulate transient failures by sending requests with intentionally malformed auth to trigger ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["401"]},", or use test card numbers that produce declines to verify your non-retry path works correctly."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"anti-patterns","__idx":8},"children":["Anti-patterns"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Mistake"},"children":["Mistake"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Why it's bad"},"children":["Why it's bad"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Fix"},"children":["Fix"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Retry on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["400"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["The request is broken — retrying won't help"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Fix the payload"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Retry on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["declined"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["The issuer said no — retrying charges the customer's card repeatedly"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Ask for a different card"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["No jitter"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["All your servers retry at the same time"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Add random jitter"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["No max retries"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Infinite loop during an outage"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Cap at 3 retries"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["No idempotency key"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Retries may create duplicate transactions"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Always send ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Idempotency-Key"]}]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"next-steps","__idx":9},"children":["Next steps"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/tutorials/error-handling/processor-specific-errors"},"children":["Processor-specific errors"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/tutorials/error-handling/log-monitor-api-errors"},"children":["Log and monitor API errors"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/concepts/api-fundamentals/error-handling"},"children":["Error handling concepts"]}]}]}]},"headings":[{"value":"Tutorial: Retry with exponential backoff","id":"tutorial-retry-with-exponential-backoff","depth":1},{"value":"Why you need retries","id":"why-you-need-retries","depth":2},{"value":"The algorithm","id":"the-algorithm","depth":2},{"value":"Visual flow","id":"visual-flow","depth":2},{"value":"When to retry","id":"when-to-retry","depth":2},{"value":"Complete implementation","id":"complete-implementation","depth":2},{"value":"Usage","id":"usage","depth":2},{"value":"Testing retries in sandbox","id":"testing-retries-in-sandbox","depth":2},{"value":"Anti-patterns","id":"anti-patterns","depth":2},{"value":"Next steps","id":"next-steps","depth":2}],"frontmatter":{"title":"Tutorial: Retry with exponential backoff","shortTitle":"Exponential backoff","intro":"Build a retry mechanism that recovers gracefully from transient API errors using exponential backoff with jitter — the industry-standard pattern for payment integrations.","type":"tutorial","seo":{"title":""}},"lastModified":"2026-06-05T02:46:44.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/docs/tutorials/error-handling/retry-with-exponential-backoff","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}