Module 6: Retries, Alerts, and Recovery

2. Safe retries in n8n 2.0

Description

By the end of this lesson you will be able to configure a node's automatic retry in n8n 2.0 — the Retry On Fail setting, with its number of attempts and its wait between them — you will understand exactly what n8n does when a node fails and gets retried, and above all you will know the rule that decides when to turn it on: a retry is only safe if the step is idempotent. You will see the difference between a transient failure, which a retry heals on its own, and an effect a retry duplicates, and you will apply that distinction to Cumbre's four workflows. You will also meet the trickiest case of all — when the effect really did happen but the response got lost — and why it's the real reason Module 2's idempotency wasn't optional.

This matters because retrying is any system's first reaction to a failure, and it's also the one that does the most damage when turned on without judgment. A network drops for half a second, an API responds with a temporary error, a service is saturated for an instant: those failures resolve themselves if you simply try again, and not retrying means losing orders for nothing. But retrying a step that creates something — a charge, a refund, a hold — doesn't repeat a harmless query: it repeats the creation. The difference between a retry that saves you and one that duplicates a customer's charge is a single condition, and this lesson is about that condition.

Connection to the module: lesson 1 gave you the map; this is the first of the five pieces. Retries are the response to failure when you can repeat the effect without harm. Lesson 3 is the flip side: what you do when you can't, and have to compensate. This lesson leans fully on Module 2 — idempotency keys, upserts, the Idempotency-Key pattern — and in a moment you'll see why that module came before this one in the guide: without idempotency, the retries you're going to configure here would be a duplicate-generating machine. Keep Cumbre's case in mind: check-credit, which places a hold, and issue-refund, which issues a refund. Those are the two protagonists.

What a retry exactly is

Let's start with the simplest part, because the word seems obvious and hides a trap.

A retry is running a failed step again, hoping it works the second time. That's it. If you call someone and the call drops before they answer, you dial again. That's a retry.

The analogy I want you to keep in mind for the whole lesson is that same call, but with a twist. Imagine you send a text message ordering a pizza, and the confirmation never arrives. You have two possibilities and can't tell which one it is: either the message never reached the pizzeria, or it did reach it and what got lost was the confirmation coming back. If you send the message again "just in case," in the first case you fixed the problem — now you really did order your pizza. But in the second case you just ordered two pizzas. And from where you're standing, the two situations look identical: in both, the confirmation never arrived.

That's the heart of everything. A retry doesn't know whether the step that failed managed to take effect before failing. It retries blindly. And that's why a retry's safety doesn't depend on the retry itself — which is always "try again" — but on how dangerous it is for the effect to happen twice. If ordering two pizzas didn't matter to you — because you had a deal with the pizzeria that two identical orders in the same minute count as one — retrying would always be safe. That deal is exactly what idempotency is.

The anatomy of Retry On Fail in n8n 2.0

n8n gives you the retry as a checkbox you turn on per node, no code required. Let's see where it lives and what each field does.

When you open any node's detail panel, besides the node's own parameters there's a Settings tab. That's where, among other options, the ones we care about live.

On exact labels. This guide was written with n8n 2.x in July 2026. Option names and their maximum values have changed between minor versions, and this is exactly the kind of thing worth verifying in your own panel instead of trusting from memory. When the exact number matters, I'll tell you and ask you to confirm it. The concept doesn't change; the label and the cap sometimes do.

On Error. It's the first dropdown, and it decides what the node does when something goes wrong, before we even talk about retries. It has three behaviors, whose exact labels are worth checking in your version but whose meaning is stable:

  • Stop the workflow (the default value): if the node fails, the whole workflow stops right there and the execution gets marked as failed. It's what you want for a critical step: if the credit hold couldn't be placed, there's no point continuing to reserve inventory.
  • Continue (with the normal output): the workflow keeps going as if the node had worked, passing whatever it got to the next node. Useful for non-critical steps — if the step that adds a cosmetic tag fails, the order can carry on.
  • Continue using the error output: the workflow keeps going, but the failed item exits through a second connection from the node, separate from the normal one. This is very powerful, because it lets you route failures down their own path without stopping the flow — we'll use this in lesson 5 for the dead-letter queue.

Retry On Fail. It's the checkbox that turns on the automatic retry. When you turn it on, the node, before giving up and applying whatever On Error says, tries itself again on its own. Turning it on brings up two more fields:

  • Max Tries: how many times, total, the node attempts before giving up. It's the number that decides whether your retry is a gentle nudge or persistent insistence. The official documentation doesn't fix a default value or a universal cap in the text I checked; in recent versions' interface the field is bounded to a small number of attempts, and this is something you should verify in your own panel, because the cap has been a moving target between versions. What doesn't change is that more attempts isn't always better: each attempt costs time and resources, and a failure that doesn't heal in two or three attempts rarely heals in ten.
  • Wait Between Tries (ms): how long the node waits between one attempt and the next, measured in milliseconds — remember, a thousand milliseconds is one second. The official documentation gives a clear example: if the API you're calling allows one request per second, set 1000 to respect that limit. As with Max Tries, the maximum allowed value is something to check in your panel.

The complete anatomy, then, is this:

Node → Settings tab
  ├─ On Error:        [ Stop workflow ▾ ]        ← what happens if it fails in the end
  ├─ Retry On Fail:   [✓]                        ← turn on the retry
  │    ├─ Max Tries:            [ 3 ]            ← how many attempts total (check the cap)
  │    └─ Wait Between Tries:   [ 1000 ] ms      ← how long to wait between attempts

A detail that's confusing at first: Max Tries counts the original attempt. If you set 3, the node tries once and, if it fails, retries two more times — three attempts total, not four. Check your panel to see if your version counts it this way, but that's the usual convention.

Why the wait matters: backoff

Let's pause on the wait field for a second, because there's more to it than it looks.

If a service is down or saturated, hammering it with immediate retries — one after another with no pause — is counterproductive: you add load right when it's weak, and your retries compete with each other and with everyone else's. That's why the wait between attempts exists. You give the service a moment to recover before touching it again.

The finer idea behind this is called backoff: instead of always waiting the same amount, you wait increasingly longer between attempts — one second, then two, then four. The logic is that if the service didn't recover in one second, it might need more, and there's no point in continuing to poke it at the same rate. n8n's Wait Between Tries field applies a fixed wait — the same interval between each attempt — which for most business-automation cases is more than enough. If someday you truly need real growing backoff, it can be built by hand with a wait node and a loop, but that's an advanced case that's rarely needed. For now, hold on to the intuition: the wait exists so you don't hit a downed service repeatedly, and a little wait is almost always better than none.

Worked example: retrying check-credit safely

Let's put retries into Cumbre's system, starting with the case where they're clearly a good idea. If you have an instance handy, follow along; if not, read it and do it later.

The check-credit workflow has, at its heart, an HTTP Request node that calls Cumbre's credit system API to place a hold for the order's amount. Remember a Module 5 restriction that still holds: calls to APIs are made with the HTTP Request node, not from a Code node — inside an n8n 2.0 Code node you cannot make HTTP requests. We use the Code node only to prepare data, like the idempotency key; the call is made by the HTTP Request.

Step 1 — The node with the effect. In check-credit, the HTTP Request node called Place credit hold makes a POST to the credit API with the order_id and the amount. This is the node that sometimes times out when the API is slow.

Step 2 — Turn on the retry. Open that node, go to the Settings tab, and turn on Retry On Fail. Set Max Tries to 3 and Wait Between Tries to 1000 (one second).

What to expect. With this, if the credit API times out on the first attempt, n8n waits one second and tries again, up to three times. If on any of those attempts the API responds fine, the workflow carries on with that response and no one finds out there was a stumble. If all three attempts fail, the node applies whatever On Error says — for a critical step like this, stopping the workflow and marking the execution as failed, which in lesson 5 we're going to catch with the Error Trigger.

Step 3 — The question that decides whether this is safe. Here's the whole lesson's point. Retrying Place credit hold is safe only if placing the hold is idempotent. And it isn't on its own: a POST that says "place a 4820-peso hold" executed twice places two holds. What makes it safe is Module 2's idempotency key.

Step 4 — The key that makes it safe. Before the HTTP Request node, a Code node computes a stable idempotency key for this order, and that key travels as a header in the request. The credit API is built so that two requests with the same key count as one — it places the hold on the first one and, on the second, returns the same hold without creating another one. With that, retrying is harmless:

// ============================================================
// Node: Code — "Build idempotency key" (before the HTTP Request)
// Mode: Run Once for Each Item
//
// INPUT:   a Cumbre order with order_id and amount
// OUTPUT:  the same item, with a stable idempotency_key field
// WHY:     the key must be the SAME on every retry,
//          so the credit API recognizes the repeated request
// ============================================================

const order = $json;

// The key is derived from data that does NOT change between retries:
// the order_id identifies the order, and "credit-hold" identifies the
// operation. So three retries of the same order share a key.
const idempotencyKey = `credit-hold:${order.order_id}`;

return {
  json: {
    ...order,
    idempotency_key: idempotencyKey,   // the HTTP Request will send it as a header
  },
};

In the HTTP Request node, that value gets set as a header — for example Idempotency-Key — using an expression that reads {{ $json.idempotency_key }}. The credit API does the rest.

What to expect from the combination. Now the retry is a pure safety net. If the API times out after having already placed the hold — the two-pizzas case — the retry sends the same key, the API recognizes that hold already exists, and returns the existing one without placing a second. The customer ends up with one hold no matter how many times it got retried. Without that key, the same retry would have placed a second hold, and you'd have turned a recovery tool into a duplicate-generating machine.

Read step 3 and step 4 together again, because they're the whole lesson: the retry didn't become safe by configuring it well; it became safe because the effect it retries is idempotent. The Retry On Fail checkbox is the easy part. The condition for turning it on is everything.

The trickiest case: when the effect happened but the response got lost

There's a specific scenario that deserves its own section, because it's the one that turns an apparently harmless retry into a duplicate, and it's the hardest one to diagnose.

Remember Module 1's "at least once" delivery and Module 2's "check then act" trap. Here they come together. When an HTTP Request node calls an API and fails, there are two possible worlds behind that failure, and n8n can't tell them apart:

World A — the request never arrived. The network dropped before the API received anything. The effect never happened. Retrying is exactly the right thing to do: now it really arrives.

World B — the request arrived, the API did the work, and what got lost was the response coming back. The hold got placed. But since the confirmation never returned, n8n sees a timeout and concludes "it failed." Retrying, in this world, places a second hold.

From n8n's point of view, world A and world B look identical: in both, the node timed out. It's impossible to know which one happened by looking at the error. And that's why you can't decide whether retrying is safe case by case, by looking at the failure. You have to make it safe by design, so it doesn't matter which of the two worlds you're in. That's, once again, the idempotency key: with it, world B stops being a problem, because the second attempt with the same key creates nothing new.

This is the deep reason Module 2 came before this one. You didn't learn idempotency as an isolated topic; you learned it so that, here, you could turn on the Retry On Fail checkbox with no fear. A retry on an idempotent effect is a safety net. A retry on an effect that isn't is Russian roulette that fires in world B.

Retrying the node vs. retrying the execution

There are two different levels at which n8n can retry, and it's worth not confusing them.

Node retry (this lesson's): the Retry On Fail setting you just saw. It happens inside an execution, automatically, while the workflow is running. The node fails, waits, retries, and if it works, the execution carries on as if nothing happened. It's the front-line response to a transient failure.

Full execution retry: when an execution has already finished marked as failed, n8n lets you relaunch it entirely from the executions list. There you'll see options like Retry with original workflow (retry with the workflow as it was) and Retry with currently saved workflow (retry with the current saved version, useful if you already fixed the bug). This isn't automatic: you trigger it yourself, by hand, after the fact. It's a recovery and debugging tool, and we're going to use it in depth in lesson 6 to reproduce the duplicate bug.

The practical distinction: the node retry is for transient failures that heal themselves in seconds. The execution retry is for when the failure has already been logged and you decide, with a clear head, to reprocess it — maybe after fixing something. And watch out for the same thing as always: retrying a whole execution also re-executes the effects. If that execution managed to place a hold before failing, retrying it without idempotency places another one. The same rule governs both levels.

When NOT to retry

Just as important as knowing how to turn on the retry is knowing when to leave it off. Retrying isn't a universal default; it's a decision.

Don't retry a failure that isn't transient. If the credit API responds "this customer has no available credit line," that isn't a network stumble: it's a legitimate, final answer. Retrying it ten times gives you ten identical rejections, wasting time for nothing. Retries are for transient failures — timeouts, temporary server errors, saturation — not for logical failures that are going to give the same result every time. In terms of HTTP status codes, a 503 Service Unavailable or a 429 Too Many Requests is usually worth retrying; a 400 Bad Request or a 403 Forbidden isn't, because retrying a malformed or unauthorized request gives the same error.

Don't retry an effect you can't make idempotent. If a step creates something and you have no way to give it an idempotency key or have the API recognize duplicates, retrying is dangerous. For those cases the module's other tool exists: lesson 3's compensation. You don't force the retry; you assume it might duplicate and design how to undo it.

Don't retry so many times you hide a real problem. If a node needs eight retries to work, you don't have a transient failure: you have a service that's systematically broken and that someone should look at. Setting Max Tries too high turns a legitimate alert into prolonged silence. Two or three attempts capture almost every genuinely transient stumble; beyond that, you're probably covering up something that deserves an alert — lesson 4's subject.

Common mistakes

Turning on Retry On Fail on a node that creates something, with no idempotency key (conceptual). What happens: the retry gets turned on for the Place credit hold node or for issue-refund "to make it more robust," with no idempotency protection. Everything works in tests, because in tests the API answers on the first try. In production, one day the API responds slowly — it places the hold but the confirmation gets lost — n8n retries, and a second hold shows up. Why it happens: the retry feels like a harmless improvement, and world B — effect done, response lost — almost never happens in development, so it doesn't see it coming. How to detect it: check every node with Retry On Fail turned on and ask yourself "does this node create something?" If yes, look for the idempotency key that protects it; if it isn't there, you have a duplicate waiting to happen. How to fix it: add Module 2's idempotency key before turning on the retry, or — if the effect can't be made idempotent — turn the retry off and use lesson 3's compensation.

Retrying logical failures as if they were transient (practical). What happens: the retry gets turned on for a node that sometimes gets negative business responses — "credit rejected," "SKU doesn't exist" — and those cases get retried three times giving the same result, adding latency and cluttering the execution history with failures that weren't really failures. Why it happens: the retry doesn't distinguish between "the network failed" and "the API said no"; it retries anything the node marks as a failure. How to detect it: look at a node's execution history; if you see groups of three identical attempts failing the same way, you're retrying something that wasn't transient. How to fix it: distinguish, in your design, a transient failure from a business result. A "credit rejected" response shouldn't be a node error — it should be a normal output an If node routes — reserving the retry for real timeouts and temporary errors.

Setting the wait between attempts to zero (practical). What happens: the retry gets turned on with Wait Between Tries set to 0 "to be fast," and when the API is saturated, the three attempts go out almost simultaneously, hit the same saturated service, and all three fail within a fraction of a second, giving the service no chance to recover. Why it happens: waiting seems like wasted time, and on the happy path it is — but the retry doesn't exist for the happy path. How to detect it: if your retries all fail nearly at the same time as the original attempt, you gave them no room to help. How to fix it: set a wait of at least one second (1000), and more if the API has a known rate limit — n8n's own documentation recommends aligning the wait with that limit. A little wait almost always recovers more failures than none.

Exercises

Exercise 1 — To retry or not? For each of these five Cumbre steps, decide whether you'd turn on Retry On Fail, and why in one sentence. If you'd say yes, also say what condition needs to hold for it to be safe.

(a) An HTTP Request that checks a customer's credit line balance (read-only, changes nothing). (b) The HTTP Request that places the credit hold (Place credit hold). (c) A Code node that computes the order total from line_items. (d) issue-refund's HTTP Request that issues a refund. (e) A node that receives "credit rejected" from the API and decides not to fulfill the order.

See solution

(a) Yes, unconditionally. It's a pure read: checking a balance changes nothing, so repeating it is harmless by nature. Retrying a timeout here is always safe. This is the retry's ideal case.

(b) Yes, but with one condition: the idempotency key. Placing a hold is an effect that can be duplicated. Retrying is safe only if the request carries a stable Idempotency-Key and the API recognizes duplicates, as in the worked example. Without that, don't turn it on.

(c) Yes, though it will almost never be needed. A calculation inside a Code node calls nothing external, so it rarely "fails" for transient reasons; and if it failed because of bad data, retrying would give the same error. It doesn't hurt to turn it on, but it doesn't help either: the retry is unnecessary here.

(d) Yes, with the strictest condition of all: the idempotency key. Issuing a refund is the system's most dangerous effect to duplicate — money going out twice. Retrying is safe only with a solid Idempotency-Key. This node is the one that's going to star in lesson 6's duplicate bug, precisely because it's where getting it wrong hurts the most.

(e) No. "Credit rejected" isn't a transient failure: it's a final business response. Retrying it ten times gives ten rejections. This case shouldn't even be treated as a node error; it should be a normal output an If routes toward "order not fulfilled."

Why this works: notice the pattern. Reads (a, c) are safe to retry almost by definition. Effects (b, d) are safe only with idempotency. And business results (e) never get retried, because they aren't transient. Those three categories — read, effect, business result — are all you need to decide.

Exercise 2 — World A and world B. Cumbre's Place credit hold node times out. Describe exactly what happens when n8n retries it, in each of these two scenarios, and say in which of the two the idempotency key makes the difference:

(a) The request never reached the API (world A). (b) The request arrived, the hold got placed, and what got lost was the response coming back (world B).

See solution

(a) World A behaves the same with and without a key. Since the request never arrived, no hold has been placed. The retry sends the request again, this time it arrives, and the hold gets placed for the first time. Result: one hold. The idempotency key changes nothing here, because there was no duplicate to avoid.

(b) World B is where the key decides everything. Without a key: the retry sends a new request, the API sees it as a different hold request, and places a second hold. Result: two holds, the customer with double blocked. With a key: the retry sends the same Idempotency-Key as the original attempt; the API recognizes it already processed that key and returns the existing hold without creating another. Result: one hold.

The conclusion that matters: since n8n can't tell world A apart from world B — both look like a timeout — you can't decide by hand whether retrying is safe. The idempotency key makes it not matter which of the two worlds you're in: in both, you end up with exactly one hold. That's designing safety, instead of guessing at it.

Why this works: this exercise is the lesson's heart put into two concrete cases. World B is rare — it almost never happens in tests — and that's why it's so dangerous: it doesn't see it coming, and when it shows up in production, it produces a duplicate no one understands. The idempotency key neutralizes it without you needing to know when it happened.

Exercise 3 — Design the system's retry policy. For Cumbre's four workflows (order-triage, check-credit, inventory-sync, issue-refund), write a retry policy: on which nodes you'd turn on Retry On Fail, with what safety condition, and on which you'd leave it off and why. Don't write code; write the decisions.

See solution

A reasonable policy, with the decisions argued:

order-triage. The node that checks the ledger to deduplicate (a Postgres read) can be retried unconditionally: reading does no harm. Writing to the ledger to record the seen order_id is also worth retrying, but only because it's idempotent by design — writing the same order_id twice with a uniqueness constraint doesn't create two records, as you saw in Module 4. The contract validation nodes (Module 3) are computations: retrying them neither helps nor hurts.

check-credit. The Place credit hold node carries Retry On Fail turned on, with the firm condition of the idempotency key. Max Tries at 2 or 3, with a wait of at least one second. If it keeps failing after the retries, On Error is set to "stop the workflow" so lesson 5 can catch it.

inventory-sync. It depends on how the reservation is written. If it's an idempotent upsert by order_id (recommended in Module 2), retry it without fear. If for some reason it were a non-idempotent decrement, the retry doesn't get turned on: it gets compensated (lesson 3) or rewritten as an upsert first.

issue-refund. Retry On Fail turned on, with the most carefully built idempotency key in the system, because duplicating a refund is lost money. It's the node where the safety condition is non-negotiable.

What stays off across the whole system: any node that receives negative business responses (credit rejected, SKU doesn't exist, out of stock) carries no retry, because those aren't transient failures but results that need routing with an If.

Why this works: a retry policy isn't "turn it on everywhere" or "never turn it on." It's a per-node decision that answers two questions: "is the failure here transient?" and "if it has an effect, is it idempotent?" Being able to write that policy for a real system is exactly what separates someone who configures checkboxes from someone who designs a system's reliability — and it's, again, the kind of thing that gets defended in an interview while looking at your workflow.

Summary and next step

In this lesson you saw that a retry is simply trying a failed step again, and that its danger isn't in the retry but in the effect: with the image of the pizza text message with no confirmation, you understood that blindly retrying fixes the case where the effect never happened, but duplicates the case where it did and only the response got lost. You learned the anatomy of Retry On Fail in n8n 2.0's Settings tab — On Error with its three behaviors, Max Tries and Wait Between Tries, with the warning to check the caps in your own panel — and the idea of backoff to avoid hammering a downed service. You configured the retry for Cumbre's check-credit and saw that what makes it safe isn't the checkbox, but Module 2's idempotency key traveling in the header. And you dissected the trickiest case, world A vs. world B, which is the real reason idempotency came before this module in the guide.

Before moving on you should be able to: turn on Retry On Fail on a node and explain what each of its fields does; say why retrying an effect with no idempotency key is dangerous; and classify a node in your system as "retry unconditionally" (read), "retry only with idempotency" (effect), or "don't retry" (business result or non-transient failure).

What you haven't seen yet is what to do with effects you can't make idempotent. Because not everything can be protected with a key: sometimes a charge already happened, an email already got sent, a hold already got placed, and the next step fails, and there's no way to "un-happen" it. Lesson 3 is the other half of the failure response: when you can't avoid the effect happening, you undo it. You'll see the compensating-actions pattern — for every "create" an "undo" — how issue-refund becomes the compensation for a hold left orphaned, and why a perfect rollback almost never exists in a system that touches the real world.

Resources