The need: run it in 3 days, unless
A day-3 reminder for an unpaid invoice has to stay silent if Stripe already says paid. A follow-up to a new lead should die the moment they reply. The reminder the day before an appointment goes away if the booking is cancelled.
Each case has two sides. One workflow starts the delay when the invoice is created. Another one hears about the customer, through Stripe’s payment webhook or a new email in the inbox. The hard part is the link between them, because the second workflow has to find the first one’s pending step and stop it.
Whatever you pick, keep a quick status check right before the send. A stop or a cancel can cross the send by a second, and customers sometimes pay through a channel your trigger never sees.
Option A: Wait, then check before sending
The simplest build. After the trigger, add a Wait node with Resume set to After Time Interval, 3 days. After it, read the current state by your own ID (the Stripe node, or an HTTP Request to your CRM), then an IF: still unpaid goes on to the send, anything else ends the branch.
There’s nothing to cancel. The run wakes up, looks, and does nothing if the customer already acted. For a few dozen leads a week this is often all you need, and it’s the easiest one to debug.
What you give up: every lead is an execution sitting in Waiting for three days. The Wait node docs say waits under 65 seconds aren’t offloaded to the database, which means longer ones are, so a restart doesn’t lose them. A waiting run resumes with the copy of the workflow saved with the execution (that’s how n8n’s wait tracker loads it), so a fix you publish on Tuesday won’t reach the leads that started on Monday. And you can’t change the timer of a run that’s already waiting: people have asked since 2022, and the Enhanced Wait/Resume request is still open.
If you use At Specified Time, note that the Wait node uses the n8n server’s time, whatever time zone the workflow settings say.
Option B: stop the waiting execution with the n8n API
Since n8n 2.10.0 (23 February 2026, listed as “Expose execution stopping in public API”), the public API has POST /api/v1/executions/{id}/stop. It’s in the official API reference under Executions, authenticated with the X-N8N-API-KEY header. It answers the long-running Cancel Running Workflow Execution via n8n API thread.
The catch is the ID. The workflow that hears “customer replied” has no idea which execution belongs to that customer, so the first workflow must save it. Right after the trigger, before the Wait, write {{ $execution.id }} next to the lead, in an n8n Data table or a CRM field. In the reply workflow, look it up and add an HTTP Request node:
HTTP Request · stop the waiting run
- Method
POST- URL
https://YOUR_N8N_HOST/api/v1/executions/EXECUTION_ID/stop, with the stored ID mapped in place ofEXECUTION_ID- Send Headers
On- Header → Name
X-N8N-API-KEY- Header → Value
- Your n8n API key (Settings → n8n API)
Check these before you build on it
Your instance must run 2.10.0 or later. On n8n Cloud the public API isn’t available during the free trial, and a self-hosted admin can turn it off. If your keys are scoped, the key needs execution:stop.
The reference doesn’t say which statuses the single stop accepts. Its bulk sibling lists queued, running and waiting, and a user testing 2.41.6 reported that the single stop works on waiting runs. Try it on one waiting test execution on your version before you rely on it.
On older versions the only API route was DELETE /api/v1/executions/{id}. It does stop a waiting run, and it also throws away its record, which is why the stop-without-deleting thread exists. Some answers point at /rest/executions/{id}/stop: that’s the editor’s internal endpoint, it wants a browser session cookie, and an API key won’t work there.
For clean-up, POST /api/v1/executions/stop takes a body with status: ["waiting"], a workflowId and startedBefore, and stops every run of that workflow waiting since before that date. It fits the runs stuck in Waiting case. It can’t target one customer, since those are the only filters.
Option B keeps everything from option A, timer and edit limits included, and adds a table of execution IDs you have to keep in sync. If saving the ID fails for one lead, that lead’s run can’t be stopped.
Option C: Wait on a webhook call, with a time limit
The Wait node can also resume On Webhook Call. Each execution gets its own URL in {{ $execution.resumeUrl }}, which the docs describe as unique to each execution. With a time limit on, the run resumes by itself when the three days are up, or earlier if someone calls that URL.
Wait
- Resume
On Webhook Call- HTTP Method
POST- Limit Wait Time
On- Limit Type
After Time Interval- Amount
3- Unit
Days
Before the Wait, save {{ $execution.resumeUrl }} with the lead. Keep the whole URL: on n8n Cloud it ends with a ?signature=… parameter. In the reply workflow, look it up and send it a POST with a small JSON body:
{
"reason": "replied"
}After the Wait, an IF on {{ $json.body?.reason }}. Empty means the time ran out, so send the follow-up. If it has a value, the customer acted: end the branch, or use the run’s data for something else.
We ran both cases on n8n Cloud 2.45.5 with a 1-minute limit. When the time runs out, the Wait passes on the item it received, unchanged, so there is no body.reason. When the resume call arrives, the Wait outputs that request instead (headers, query, body), so body.reason is replied, and the fields you had before the Wait are no longer in $json. Read them from the node that set them, for example {{ $('Save lead').item.json.email }}. If the workflow started from a Webhook trigger, that first body also travels through on a timeout, so branch on a field the trigger never sends.
This resumes the run early. It doesn’t cancel it: the execution continues to the next node either way, and your IF does the stopping. That’s handy when the customer’s action should trigger something in the same run, like moving the deal stage. Two notes from the Wait docs: a manual test run gets a different resume URL, so the node that saves it must run in the same execution as the Wait. And if the URL is ever called from outside your instance, the Wait node has its own authentication setting.
You store a URL per lead here, where option B stores an ID, and the run still sits in Waiting with the workflow version it started with.
Option D: schedule the call outside n8n, cancel by reference
Here nothing waits in n8n. Workflow 1 asks an outside scheduler to call workflow 2’s Webhook URL in three days, tagged with a reference you choose, such as invoice:1042, and ends. The workflow that hears “paid” cancels by that same reference. There’s no execution ID or resume URL to store, because the invoice ID is already the key.
We make Webhook Scheduler, so this is our option, and the setup below uses it. Any service that schedules an HTTP call and cancels it by your own reference works the same way.
With the community node
n8n-nodes-webhookscheduler is a verified community node. In the canvas, open the nodes panel, search for Webhook Scheduler, and pick it under “More from the community”, then Install (instance owners and admins only). That works on n8n Cloud too. Its operations are Schedule, Get, Get Many, Cancel and Cancel by Reference.
Workflow 1 · Webhook Scheduler → Schedule
- URL
- Workflow 2’s Webhook Production URL
- Run At
{{ $now.plus({ days: 3 }).toISO() }}- Body
{ "invoiceId": "{{ $json.invoiceId }}" }- Reference
- Under Additional Fields:
invoice:{{ $json.invoiceId }} - Idempotency Key
- Under Additional Fields:
invoice-follow-up:{{ $json.invoiceId }}. A re-run for the same invoice returns the same job, so the customer doesn’t get two follow-ups.
Payment workflow · Webhook Scheduler → Cancel by Reference
- Reference
invoice:{{ $json.invoiceId }}. Every job with that reference that hasn’t been sent yet is canceled. The output lists what was canceled and what was skipped, for example a job being delivered that very second.
With the HTTP Request node
Nothing to install. Copy each workflow below and paste it on an empty canvas, then replace wh_live_YOUR_KEY with your API key and YOUR_N8N_HOST with your instance. Workflow 2, the one that sends the follow-up, starts with a Webhook node on the path invoice-follow-up; keep the status check in it.
{
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "invoice-created",
"options": {}
},
"name": "Invoice created",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
0,
0
]
},
{
"parameters": {
"method": "POST",
"url": "https://webhookscheduler.com/api/v1/schedule",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer wh_live_YOUR_KEY"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"url\": \"https://YOUR_N8N_HOST/webhook/invoice-follow-up\",\n \"runAt\": \"{{ $now.plus({ days: 3 }).toISO() }}\",\n \"reference\": \"invoice:{{ $json.body.invoiceId }}\",\n \"idempotencyKey\": \"invoice-follow-up:{{ $json.body.invoiceId }}\",\n \"body\": { \"invoiceId\": \"{{ $json.body.invoiceId }}\" }\n}",
"options": {}
},
"name": "Schedule follow-up in 3 days",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
240,
0
]
}
],
"connections": {
"Invoice created": {
"main": [
[
{
"node": "Schedule follow-up in 3 days",
"type": "main",
"index": 0
}
]
]
}
}
}{
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "invoice-paid",
"options": {}
},
"name": "Invoice paid",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
0,
0
]
},
{
"parameters": {
"method": "GET",
"url": "https://webhookscheduler.com/api/v1/jobs",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "reference",
"value": "=invoice:{{ $json.body.invoiceId }}"
},
{
"name": "state",
"value": "open"
}
]
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer wh_live_YOUR_KEY"
}
]
},
"options": {}
},
"name": "Find open follow-ups",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
240,
0
]
},
{
"parameters": {
"fieldToSplitOut": "jobs",
"options": {}
},
"name": "One item per job",
"type": "n8n-nodes-base.splitOut",
"typeVersion": 1,
"position": [
480,
0
]
},
{
"parameters": {
"method": "POST",
"url": "=https://webhookscheduler.com/api/v1/jobs/{{ $json.id }}/cancel",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer wh_live_YOUR_KEY"
}
]
},
"options": {}
},
"name": "Cancel",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
720,
0
],
"onError": "continueRegularOutput"
}
],
"connections": {
"Invoice paid": {
"main": [
[
{
"node": "Find open follow-ups",
"type": "main",
"index": 0
}
]
]
},
"Find open follow-ups": {
"main": [
[
{
"node": "One item per job",
"type": "main",
"index": 0
}
]
]
},
"One item per job": {
"main": [
[
{
"node": "Cancel",
"type": "main",
"index": 0
}
]
]
}
}
}The cancel node is set to continue on error: a job that’s being delivered at that exact second can’t be canceled, and that shouldn’t fail the payment workflow.
What this changes: workflow 2 starts as a fresh execution when the call arrives, so it runs whatever version is published at that moment. If n8n doesn’t answer, the call is retried and every attempt lands in the delivery log with its status code. When the last retry fails too, you get an email. The pending follow-ups are listed in one place, with their reference.
What it costs: another account and an API key to manage. Workflow 2’s Production URL has to be public HTTPS, so a self-hosted n8n on localhost or an IP with a port won’t do. There’s no “edit the time” call: you cancel and schedule again. A canceled call still counts toward the monthly quota.
Full setup, with a test you watch arrive
The n8n quickstart walks through workflow 2’s Webhook node, the API key and a 2-minute test call, then the cancel in step 7. The Free plan schedules up to 7 days ahead, for 100 webhooks a month. Paid plans go up to 365 days ahead.
The four side by side
“Waiting run” means an execution that stays in n8n’s Waiting status for the whole delay.
When to keep using the Wait node
Short delays and low volume. A 10-minute pause before a Slack ping, or a two-hour wait for a handful of leads a day, doesn’t need any of this: option A with a status check is fine, and under 65 seconds n8n doesn’t even write the wait to the database. Staying inside n8n with one less account to manage is a fair reason on its own.
It starts to hurt when delays run into days and hundreds of runs sit in Waiting while you still need to change the flow. At that point the choice is between B, if you’re on 2.10.0 or later and don’t mind storing execution IDs, and D, if you’d rather cancel by the invoice or lead ID you already have.
Questions
Can I stop a waiting n8n execution through the API without deleting it?
Yes, from n8n 2.10.0. The public API has POST /api/v1/executions/{id}/stop, documented in the API reference under Executions, with your key in the X-N8N-API-KEY header. The reference does not say which statuses it accepts, so test it on one waiting run first. Before 2.10.0 the only API route was DELETE /api/v1/executions/{id}, which removes the execution and its history. The /rest/executions/{id}/stop route that forum answers mention is the editor’s internal endpoint and needs a browser session cookie, not an API key.
How do I auto-cancel executions that sit in Waiting too long?
On n8n 2.10.0 or later, a scheduled workflow can call POST /api/v1/executions/stop with status ["waiting"], the workflowId and startedBefore set to your cutoff. That stops every run of that workflow waiting since before the cutoff. It filters on workflow, status and start time only, so it cannot pick out one customer.
Can I change the wait time of an execution that is already waiting?
Not with a built-in setting. The Wait node docs describe no way to edit a waiting run, and the forum request for it (Enhanced Wait/Resume functionality) is still open. You can stop the run and start a new one, resume it early through its resume URL, or, if the delay lives outside n8n, cancel the scheduled call and schedule a new one.
Do my workflow edits apply to executions that are already waiting?
No. When a waiting run resumes, n8n loads the copy of the workflow stored with that execution, not the version you published since. A fix reaches only runs that start after it. A run that starts from a Webhook trigger when a scheduled call arrives uses the version published at that moment.
How far ahead can Webhook Scheduler schedule on the free plan?
Up to 7 days ahead, for 100 webhooks a month, canceled ones included. Paid plans schedule up to 365 days ahead.