{
  "id": "yCrYxATDJ17upNhY",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Reusable Retry Handler - Exponential Backoff, Jitter, Notifications, Suspend",
  "tags": [
    {
      "id": "tM2BVCBK8TQHwIyC",
      "name": "resilience",
      "createdAt": "2026-04-28T01:22:33.852Z",
      "updatedAt": "2026-04-28T01:22:33.852Z"
    },
    {
      "id": "5RJ1TQycQr3pHG5c",
      "name": "error-handling",
      "createdAt": "2026-04-28T00:02:06.158Z",
      "updatedAt": "2026-04-28T00:02:06.158Z"
    },
    {
      "id": "47RJUhI8wCGtedIg",
      "name": "retry",
      "createdAt": "2026-04-28T01:22:33.814Z",
      "updatedAt": "2026-04-28T01:22:33.814Z"
    }
  ],
  "nodes": [
    {
      "id": "c0345ede-b82e-488e-9686-d6426fa83654",
      "name": "Sticky Note - Retry Policy",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -992,
        -160
      ],
      "parameters": {
        "width": 856,
        "height": 1834,
        "content": "# Reusable Retry Handler with Exponential Backoff, Jitter, Slack/Email Alerts, and Manual Suspend\n\n## How it works\n\nThis workflow is a reusable retry and resilience pattern for n8n.\n\nIt can be called from any workflow using the Execute Workflow node. It runs a transient operation, classifies the result, retries retryable failures using exponential backoff with jitter, and stops retrying after a configurable maximum number of attempts.\n\nAt a high level, it:\n\n\u2022 Accepts a generic operation input from another workflow\n\u2022 Runs an HTTP/API operation that can be replaced with any app node\n\u2022 Retries only transient failures such as 408, 409, 425, 429, 500, 502, 503, and 504\n\u2022 Avoids retrying common permanent errors such as 400, 401, 403, 404, and 422\n\u2022 Calculates exponential backoff delay with random jitter to reduce retry storms\n\u2022 Sends a Slack message when all retries are completed\n\u2022 Sends an email notification when all retries are completed\n\u2022 Suspends the workflow at a Wait node for manual review\n\u2022 Optionally continues to Stop and Error so your global Error Workflow can capture the final failure\n\nThis template is useful for production workflows that call third-party APIs, SaaS apps, databases, or internal services that may fail temporarily.\n\n## Set up steps\n\nSetup should take around 10\u201315 minutes.\n\n\u2022 Import the workflow JSON into n8n\n\u2022 Configure Slack credentials in the Slack alert node\n\u2022 Configure SMTP credentials in the email alert node\n\u2022 Replace the demo HTTP Request node with your real API/app operation if needed\n\u2022 Set your default max attempts, base delay, max delay, jitter percentage, Slack channel, and email recipient\n\u2022 Call this workflow from other workflows using Execute Workflow\n\u2022 Use n8n credentials or environment variables for secrets; do not hardcode API keys\n\nThis template implements a generic retry wrapper for n8n workflows:\n\n- Exponential backoff\n- Random jitter\n- Max attempts\n- Retryable/non-retryable classification\n- Slack alert when retries are exhausted\n- Email alert when retries are exhausted\n- Suspend/wait for manual review after retries are exhausted\n- Optional final Stop and Error so a global Error Workflow can capture the failed execution\n\n## Default retry policy\n\nRetryable by default: 408, 409, 425, 429, 500, 502, 503, 504.\n\nNot retried by default: 400, 401, 403, 404, 422.\n\n## Backoff formula\n\n```text\nwaitSeconds = min(maxDelaySeconds, baseDelaySeconds * 2^(attempt - 1))\nfinalWait = waitSeconds +/- random(jitterPercent)\n```\n\n## How to use\n\n1. Import the template into n8n.\n2. Configure Slack credentials in the Slack node.\n3. Configure SMTP credentials in the Email node.\n4. Replace the demo HTTP Request node with your real operation, or pass HTTP inputs into the template.\n5. From another workflow, call this workflow using Execute Workflow.\n\n## Example input\n\n```json\n{\n  \"operationName\": \"Create customer in CRM\",\n  \"url\": \"https://api.example.com/customers\",\n  \"method\": \"POST\",\n  \"headers\": {\n    \"Authorization\": \"Bearer {{$env.API_TOKEN}}\"\n  },\n  \"body\": {\n    \"name\": \"Acme\"\n  },\n  \"maxAttempts\": 5,\n  \"baseDelaySeconds\": 30,\n  \"maxDelaySeconds\": 900,\n  \"jitterPercent\": 25,\n  \"notifyEmail\": \"ops@example.com\",\n  \"slackChannel\": \"#automation-alerts\"\n}\n```\n\n## Suspended workflow behavior\n\nAfter all retries are completed, the workflow sends Slack and email notifications, then pauses at a Wait node. This keeps the failed execution suspended for manual review. After review, resume the workflow using the Wait node resume URL. It then reaches Stop and Error, allowing your global error workflow to capture the final failure.\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "99351134-0d4c-40e9-a1bd-6b22570caca9",
      "name": "Execute Workflow Trigger",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "notes": "Sub-workflow entry point. Call this retry wrapper from any workflow.",
      "position": [
        32,
        784
      ],
      "parameters": {},
      "notesInFlow": true,
      "typeVersion": 1
    },
    {
      "id": "0a43ab16-0b04-4cda-8c2a-07fac5079431",
      "name": "Normalize Input + Defaults",
      "type": "n8n-nodes-base.code",
      "notes": "Sets defaults for retry/backoff, notification routing, and correlation ID.",
      "position": [
        272,
        784
      ],
      "parameters": {
        "jsCode": "const input = $json;\nconst cfg = {\n  operationName: input.operationName || 'Generic transient operation',\n  url: input.url,\n  method: (input.method || 'GET').toUpperCase(),\n  headers: input.headers || {},\n  query: input.query || {},\n  body: input.body ?? {},\n  maxAttempts: Number(input.maxAttempts || 5),\n  baseDelaySeconds: Number(input.baseDelaySeconds || 30),\n  maxDelaySeconds: Number(input.maxDelaySeconds || 900),\n  jitterPercent: Number(input.jitterPercent ?? 25),\n  notifyEmail: input.notifyEmail || '',\n  slackChannel: input.slackChannel || '#automation-alerts',\n  correlationId: input.correlationId || `${Date.now()}-${Math.random().toString(16).slice(2)}`,\n  attempt: Number(input.attempt || 1),\n  startedAt: input.startedAt || new Date().toISOString(),\n  retryableStatusCodes: input.retryableStatusCodes || [408,409,425,429,500,502,503,504],\n  workflowName: $workflow.name,\n  executionId: $execution.id,\n};\nif (!cfg.url) throw new Error('Missing required input: url');\nreturn [{ json: cfg }];"
      },
      "notesInFlow": true,
      "retryOnFail": false,
      "typeVersion": 2
    },
    {
      "id": "17116af5-c245-448e-a846-756fdbe28135",
      "name": "Do Operation - Replace With Your Node",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Demo operation. Replace with your actual HTTP/API/app node. Never hardcode secrets.",
      "onError": "continueRegularOutput",
      "position": [
        576,
        704
      ],
      "parameters": {
        "url": "={{$json.url}}",
        "method": "={{$json.method}}",
        "options": {
          "timeout": 30000,
          "response": {
            "response": {
              "neverError": true,
              "fullResponse": true
            }
          }
        },
        "jsonBody": "={{JSON.stringify($json.body || {})}}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "x-correlation-id",
              "value": "={{$json.correlationId}}"
            }
          ]
        }
      },
      "notesInFlow": true,
      "typeVersion": 4.2
    },
    {
      "id": "26f802d8-b969-4924-b4b6-543d2550477b",
      "name": "Classify Result + Calculate Backoff",
      "type": "n8n-nodes-base.code",
      "notes": "Classifies result and calculates exponential backoff with jitter.",
      "position": [
        848,
        800
      ],
      "parameters": {
        "jsCode": "let cfg;\n\ntry {\n  const nextItems = $items('Prepare Next Attempt');\n\n  if (nextItems && nextItems.length > 0 && nextItems[0]?.json) {\n    cfg = nextItems[0].json;\n  } else {\n    cfg = $items('Normalize Input + Defaults')[0].json;\n  }\n} catch (e) {\n  cfg = $items('Normalize Input + Defaults')[0].json;\n}\n\nconst response = $json;\nconst statusCode = Number(response.statusCode || response.status || 0);\nconst ok = statusCode >= 200 && statusCode < 300;\nlet errorMessage = '';\nif (!ok) {\n  errorMessage = response.statusMessage || response.message || response.error?.message || `HTTP ${statusCode || 'unknown error'}`;\n}\nconst retryable = !ok && cfg.retryableStatusCodes.includes(statusCode);\nconst attemptsRemaining = cfg.attempt < cfg.maxAttempts;\nconst shouldRetry = retryable && attemptsRemaining;\nconst rawDelay = Math.min(cfg.maxDelaySeconds, cfg.baseDelaySeconds * Math.pow(2, cfg.attempt - 1));\nconst jitterRange = rawDelay * (cfg.jitterPercent / 100);\nconst jitter = Math.round((Math.random() * 2 - 1) * jitterRange);\nconst delaySeconds = Math.max(1, Math.round(rawDelay + jitter));\nreturn [{ json: { ...cfg, statusCode, success: ok, retryable, shouldRetry, attemptsRemaining, delaySeconds, nextAttempt: cfg.attempt + 1, errorMessage, responseBody: response.body ?? response, lastAttemptAt: new Date().toISOString() }}];"
      },
      "notesInFlow": true,
      "typeVersion": 2
    },
    {
      "id": "92f16e5d-395c-464f-8ea8-5b5b2acef35b",
      "name": "Success?",
      "type": "n8n-nodes-base.if",
      "position": [
        1104,
        784
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "36755111-c95b-4a50-9a40-ba1aa95793eb",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{$json.success}}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "c6c19f84-dad8-4e3b-857a-8218b0822e89",
      "name": "Retryable and Attempts Left?",
      "type": "n8n-nodes-base.if",
      "position": [
        1376,
        704
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "54728007-5ced-450c-b572-f4bbb97aa98e",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{$json.shouldRetry}}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "ef619caf-1a62-44e4-a2c3-98d76587b2ef",
      "name": "Wait - Exponential Backoff Delay",
      "type": "n8n-nodes-base.wait",
      "notes": "Suspends this execution for the calculated delay, then resumes with the same data.",
      "position": [
        1536,
        816
      ],
      "parameters": {
        "amount": "={{ $json.delaySeconds}}"
      },
      "notesInFlow": true,
      "typeVersion": 1.1
    },
    {
      "id": "69a0b607-715a-481d-8d76-a924984e7c3d",
      "name": "Prepare Next Attempt",
      "type": "n8n-nodes-base.code",
      "position": [
        1728,
        912
      ],
      "parameters": {
        "jsCode": "return [{ json: { ...$json, attempt: $json.nextAttempt, previousAttempts: [ ...($json.previousAttempts || []), { attempt: $json.attempt, statusCode: $json.statusCode, errorMessage: $json.errorMessage, delaySeconds: $json.delaySeconds, at: $json.lastAttemptAt } ] }}];"
      },
      "typeVersion": 2
    },
    {
      "id": "ed7f90a3-b6d8-4c75-a6c7-071f337772a7",
      "name": "Return Success Payload",
      "type": "n8n-nodes-base.code",
      "position": [
        1392,
        528
      ],
      "parameters": {
        "jsCode": "return [{ json: { outcome: 'success', operationName: $json.operationName, correlationId: $json.correlationId, attemptsUsed: $json.attempt, statusCode: $json.statusCode, responseBody: $json.responseBody, completedAt: new Date().toISOString() }}];"
      },
      "typeVersion": 2
    },
    {
      "id": "9081af81-5469-4819-adc7-d810bada8337",
      "name": "Build Final Failure Summary",
      "type": "n8n-nodes-base.code",
      "notes": "Creates the final failure payload after all retries are completed.",
      "position": [
        2048,
        704
      ],
      "parameters": {
        "jsCode": "function redact(obj) {\n  const blocked = ['authorization','api_key','apikey','token','password','secret','client_secret'];\n  if (obj === null || obj === undefined) return obj;\n  if (Array.isArray(obj)) return obj.map(redact);\n  if (typeof obj === 'object') {\n    return Object.fromEntries(Object.entries(obj).map(([k,v]) => {\n      if (blocked.some(b => k.toLowerCase().includes(b))) return [k, '***REDACTED***'];\n      return [k, redact(v)];\n    }));\n  }\n  return obj;\n}\nconst failure = {\n  outcome: 'retry_exhausted',\n  severity: 'HIGH',\n  operationName: $json.operationName,\n  correlationId: $json.correlationId,\n  executionId: $json.executionId,\n  workflowName: $json.workflowName,\n  attemptsUsed: $json.attempt,\n  maxAttempts: $json.maxAttempts,\n  statusCode: $json.statusCode,\n  errorMessage: $json.errorMessage,\n  retryable: $json.retryable,\n  startedAt: $json.startedAt,\n  failedAt: new Date().toISOString(),\n  lastResponse: redact($json.responseBody),\n  input: redact({ url: $json.url, method: $json.method, headers: $json.headers, query: $json.query, body: $json.body }),\n  previousAttempts: $json.previousAttempts || [],\n  slackChannel: $json.slackChannel,\n  notifyEmail: $json.notifyEmail\n};\nreturn [{ json: failure }];"
      },
      "notesInFlow": true,
      "typeVersion": 2
    },
    {
      "id": "5590c3c8-70fd-46f4-9135-70dbc73b85e3",
      "name": "Send Slack Alert",
      "type": "n8n-nodes-base.slack",
      "notes": "Configure Slack credentials and channel after import.",
      "position": [
        2240,
        560
      ],
      "parameters": {
        "text": "=\ud83d\udea8 *n8n retries exhausted*\\n*Operation:* {{$json.operationName}}\\n*Workflow:* {{$json.workflowName}}\\n*Attempts:* {{$json.attemptsUsed}}/{{$json.maxAttempts}}\\n*Status:* {{$json.statusCode}}\\n*Error:* {{$json.errorMessage}}\\n*Correlation ID:* {{$json.correlationId}}\\n*Execution:* {{$json.executionId}}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "#n8n"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "notesInFlow": true,
      "typeVersion": 2.3
    },
    {
      "id": "6f5de589-deee-45c5-955c-17db2bc35f1f",
      "name": "Send Email Alert",
      "type": "n8n-nodes-base.emailSend",
      "notes": "Configure SMTP credentials after import.",
      "position": [
        2304,
        800
      ],
      "parameters": {
        "html": "=Click to resume workflow {{$execution.resumeUrl}}",
        "options": {},
        "subject": "=n8n retries exhausted: {{$json.operationName}}\n\n{{$execution.resumeUrl}}",
        "toEmail": "=vr.aus12@gmail.com",
        "fromEmail": "user@example.com"
      },
      "credentials": {
        "smtp": {
          "name": "<your credential>"
        }
      },
      "notesInFlow": true,
      "typeVersion": 2.1
    },
    {
      "id": "dc81b662-a28c-40a4-a06f-9c646afcc93e",
      "name": "Suspend - Wait for Manual Review",
      "type": "n8n-nodes-base.wait",
      "notes": "This suspends the execution after notifications are sent. Use the generated resume URL to continue after manual review.",
      "position": [
        2608,
        624
      ],
      "parameters": {
        "resume": "webhook",
        "options": {}
      },
      "notesInFlow": true,
      "typeVersion": 1.1
    },
    {
      "id": "97a2f47d-0ab3-42e6-af43-1c8ae83b44e7",
      "name": "Stop and Error After Manual Review",
      "type": "n8n-nodes-base.stopAndError",
      "notes": "Forces a final failure so your global Error Workflow can capture this if required.",
      "position": [
        2624,
        832
      ],
      "parameters": {
        "errorMessage": "=Retries exhausted for {{$json.operationName}} after {{$json.maxAttempts}} attempts. Correlation ID: {{$json.correlationId}}"
      },
      "notesInFlow": true,
      "typeVersion": 1
    },
    {
      "id": "ebb0ad2b-972f-41d6-a245-979e1b0cca64",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -64,
        624
      ],
      "parameters": {
        "color": 7,
        "width": 768,
        "height": 416,
        "content": "## Workflow tigger and API call"
      },
      "typeVersion": 1
    },
    {
      "id": "55dc6663-c9cd-4ce9-b945-c3d61dc937e5",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        752,
        464
      ],
      "parameters": {
        "color": 7,
        "width": 1184,
        "height": 768,
        "content": "## Retry check"
      },
      "typeVersion": 1
    },
    {
      "id": "a421aba9-5dc7-41fe-a8c4-77ba73573043",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1984,
        464
      ],
      "parameters": {
        "color": 7,
        "width": 1552,
        "height": 864,
        "content": "## Alert and suspend workflow on retries exhaution"
      },
      "typeVersion": 1
    }
  ],
  "active": true,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "6e90a3bb-1bd2-4f41-bb4b-e4f35a4561fd",
  "connections": {
    "Success?": {
      "main": [
        [
          {
            "node": "Return Success Payload",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Retryable and Attempts Left?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Email Alert": {
      "main": [
        [
          {
            "node": "Suspend - Wait for Manual Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Slack Alert": {
      "main": [
        [
          {
            "node": "Send Email Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Next Attempt": {
      "main": [
        [
          {
            "node": "Do Operation - Replace With Your Node",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute Workflow Trigger": {
      "main": [
        [
          {
            "node": "Normalize Input + Defaults",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Input + Defaults": {
      "main": [
        [
          {
            "node": "Do Operation - Replace With Your Node",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Final Failure Summary": {
      "main": [
        [
          {
            "node": "Send Slack Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Retryable and Attempts Left?": {
      "main": [
        [
          {
            "node": "Wait - Exponential Backoff Delay",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Final Failure Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Suspend - Wait for Manual Review": {
      "main": [
        [
          {
            "node": "Stop and Error After Manual Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait - Exponential Backoff Delay": {
      "main": [
        [
          {
            "node": "Prepare Next Attempt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Result + Calculate Backoff": {
      "main": [
        [
          {
            "node": "Success?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Do Operation - Replace With Your Node": {
      "main": [
        [
          {
            "node": "Classify Result + Calculate Backoff",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}