n8n Wait Node Not Resuming? 6 Causes, Tested

automation
Four vintage bentwood chairs against a burgundy wall in an empty waiting room, one holding a folded mustard raincoat, with a brass service bell on a side table, a metaphor for an n8n Wait node waiting for a resume webhook call
An n8n Wait node set to resume on webhook call sits in the database until the exact resume URL is called with the right method. Most stuck waits are a wrong bell, not a broken node.

When an n8n Wait node set to On Webhook Call never resumes, the node is almost never broken. In our tests on n8n 2.44.6, every stuck resume came down to the URL being called wrong (missing token, wrong method, wrong host) or the execution no longer waiting at all. The two things that surprised us were timing: a 10-second Limit Wait Time fired after as long as 51 seconds, and a 30-second wait disappeared when the container restarted. We ran all of this on October 27, 2026 in a throwaway Docker container, and every status code below comes from that run.

How does the n8n Wait node resume on a webhook call?

When the workflow reaches the Wait node, n8n saves the execution to the database with status waiting and generates a resume URL that is unique to that execution. You reach it through $execution.resumeUrl, usually in a node before the Wait node: a Respond to Webhook node, an email with an approval link, or an HTTP Request that hands the URL to another system.

Here's what our test workflow returned (Webhook → Respond to Webhook → Wait → Done):

{
  "resumeUrl": "http://localhost:5699/webhook-waiting/2?signature=59427b97e5a4...",
  "executionId": "2"
}

Two details matter here. The path is /webhook-waiting/<executionId>, and there's a signature query parameter on the end. That parameter is where most "not working" reports start.

Why does the resume URL return 401 Invalid token?

The resume URL is only valid with its token. We called the same execution three ways:

  • Full $execution.resumeUrl with POST: 200 {"message":"Workflow was started"}, and the execution finished with status success
  • Same URL without the query string: 401 {"error":"Invalid token"}
  • Same URL with a made-up signature: 401 {"error":"Invalid token"}

So if you're building the URL by hand (https://n8n.example.com/webhook-waiting/{{ $execution.id }}), it won't work. The same thing happens when something between n8n and the caller drops query parameters: a link shortener, an email template that URL-encodes the ?, or a CRM field that trims URLs. Send $execution.resumeUrl exactly as n8n generates it, and if it passes through another tool, log what actually arrives.

The token is checked with a constant-time comparison against a value stored on the execution, so there's no partial match and no way around it. That's worth having, because execution IDs are sequential numbers (ours were 1, 2, 3...) and anyone can guess the next one.

Why does it return 404 for an execution that is waiting?

404 on a waiting execution means the method or path doesn't match the Wait node. Our Wait node was set to POST. A GET to the correct, signed URL returned:

404 {"code":404,"message":"The workflow for execution \"2\" does not contain a waiting webhook with a matching path/method."}

The execution stayed waiting, and a POST right afterwards resumed it normally. A failed attempt doesn't consume anything.

This one hits approval-link flows constantly. Someone clicks a link in an email, the browser sends a GET, and the Wait node was left at its POST default. Either set HTTP Method to GET for click-to-approve links, or send people to an n8n Form instead.

The other 404 cause is Webhook Suffix. If you set one (useful when a workflow has more than one Wait node), n8n's docs say plainly that the generated resume URL won't include it. You have to append it yourself. And if you swap /webhook-waiting/ for /webhook/ because it "looks like the normal webhook path", you get a plain 404, which we confirmed too.

Why does the resume URL point to localhost?

n8n builds the resume URL from WEBHOOK_URL. We started our container with WEBHOOK_URL=http://localhost:5699/, and the resume URL used that host exactly. Behind a reverse proxy, without that variable, n8n falls back to its own protocol, host and port, so the URL you send to a client or a third-party API says http://localhost:5678/webhook-waiting/.... Nobody outside the box can reach that.

Set WEBHOOK_URL to your public HTTPS address on every n8n instance that receives traffic. If your normal production webhooks already work through the proxy, check whether they're using the same host the resume URL shows. We've seen setups where the main webhooks were registered by hand in a third-party tool with the right domain, so nobody noticed that the generated URLs were wrong until the first Wait node shipped.

Why does Limit Wait Time fire late?

This is the one that looks most like a bug. A Wait node on webhook call with a 10-second limit resumed after 11.4 s, 31.7 s and 51.1 s in three runs we started 20 seconds apart.

The reason is in how n8n 2.44.6 tracks waiting executions. Webhook waits always go to the database, even short ones. A wait tracker on the main instance queries the database every 60 seconds for executions due within the next 70 seconds and sets a timer for each one. If your limit expires between two polls, the execution is picked up on the next poll and resumes right away, already late. So the real limit is anywhere from your value to roughly your value plus 60 seconds.

Time-interval waits behave differently, and they're precise. Our 30-second interval finished in 30.1 s and our 90-second interval in 90.1 s. Short interval waits sleep in the process, and long ones are picked up by a poll ahead of time with a timer set for the exact moment.

What this means in practice:

  • Don't use Limit Wait Time as a stopwatch. Fine for "escalate if no reply in 4 hours". Not fine for "give the caller 15 seconds to confirm".
  • Don't set the limit shorter than the sender needs. If the limit fires first, the next resume call gets a 409.
  • Waits with no limit stay waiting forever. n8n stores them with a waitTill of the year 3000. One of our test executions was still sitting there when we deleted the container. Add a limit and route the timeout branch somewhere a person will see it.

Does a Wait node survive an n8n restart?

Only if it's in the database. We started three executions, waited 8 seconds, then ran docker restart with Docker's default 10-second stop timeout:

  • 30-second time-interval wait: status crashed. It was sleeping inside the process, so it died with the process.
  • 90-second time-interval wait: success, finished at 90.1 s, right on time despite the restart.
  • Webhook wait (no limit): still waiting after the restart, and the resume call worked normally.

The cutoff is 65 seconds (MAX_IN_PROCESS_WAIT_MS in the n8n source). Interval waits shorter than that never touch the database. n8n's docs say the Wait node "offloads the execution data to the database" when it pauses, which is true for webhook waits and long intervals but not for short ones.

The crash wasn't really n8n's fault. On shutdown n8n waits up to N8N_GRACEFUL_SHUTDOWN_TIMEOUT (30 seconds by default) for running executions to finish. Docker kills the container after 10 seconds unless told otherwise. When we repeated the test with docker stop -t 45, the logs showed Waiting for 1 active executions to finish... and the 30-second wait completed with success.

If you deploy n8n by restarting containers, this matters on every deploy. Set stop_grace_period: 45s in Compose (or the equivalent in your orchestrator), and for anything you can't afford to lose, use a wait of 65 seconds or more so it's stored in the database. We walk through restart and rollback hygiene in our n8n upgrade and rollback guide.

Why does it say "The execution has finished already"?

409 means the execution isn't waiting anymore. After a successful resume, calling the same URL again returned:

409 {"code":409,"message":"The execution \"2 has finished already."}

(The missing closing quote is n8n's, not ours.) You'll see this when someone clicks an approval link twice, when a sender retries after a slow response, or when the time limit fired first. Other 409s you can get from the same handler are "is running already" (the resume arrived while the execution was mid-run) and "has finished with error".

Make the caller treat 409 as "already handled", not as a failure to retry. If the caller is your own HTTP Request node, turn off retries for that call or add a check that ignores 409. Senders that retry on every non-2xx can fire duplicate work the same way webhooks do.

Our checklist for a Wait node that won't resume

When a client tells us "the approval step is stuck", this is the order we work in. It usually takes five minutes.

  1. Open the execution. If it says Crashed or Error, stop: the URL can't wake it. Find out what ended it (often a restart, see above, or a stuck execution that was cleaned up).
  2. Get the URL that was actually sent. From the email, the CRM field or the HTTP Request input. Compare it character by character with $execution.resumeUrl. Missing ?signature= means 401.
  3. Check the method and suffix. Browser clicks are GET. Suffixes aren't added for you.
  4. Check the host. If it says localhost or an internal IP, fix WEBHOOK_URL and restart.
  5. Check timing. Did the limit fire before the call arrived? Remember it can fire up to about a minute after the value you set.
  6. Check the response the caller got. 401, 404 and 409 all mean different things, and most senders log them.

For approval gates in AI agent workflows, the node choice matters as much as the debugging. Send and Wait for Response handles the link, the method and the form for you, which removes causes 2 and 3 entirely. We cover when to use which in our n8n human-in-the-loop approval guide, and if you're exposing resume URLs to third parties, the Wait node supports the same Basic, Header and JWT auth options as the Webhook node.

How we tested this

We ran n8nio/n8n:2.44.6 in Docker with SQLite, WEBHOOK_URL set, and four workflows imported and published through the n8n CLI: a webhook wait with no limit, a webhook wait with a 10-second limit, and 30-second and 90-second interval waits. Each started from a Webhook node with a Respond to Webhook node returning $execution.resumeUrl. We drove everything with curl and read status and timing from execution_entity in the database. The 65-second cutoff, the 60-second poll and the 70-second look-ahead come from the shipped source in the same image. The default N8N_GRACEFUL_SHUTDOWN_TIMEOUT of 30 seconds is also listed in n8n's deployment environment variables. The Wait node options are documented in the official Wait node docs.

If your workflows rely on approvals, callbacks or long waits and you'd rather not find these out in production, get a Free Automation Audit. We'll go through your Wait nodes, timeouts and deploy settings and tell you which ones will break first. Or see how we build and run n8n workflow automation for clients.