2026-10-24
How to get an alert when an n8n workflow stops running
n8n's Error Trigger only runs when an execution fails. Put an HTTP Request heartbeat at the end of the success path so a workflow that never starts still pages you.
An n8n workflow that errors can call another workflow. An n8n workflow that never starts cannot. The Error Trigger is wired to failed executions, not to the absence of an execution. If the schedule does not fire, the workflow is inactive, a polling trigger stops asking, or a credential expires before a run is created, there is no error object to hand to anyone. The editor looks the way you left it. The last execution in the list is still green.
What the Error Trigger actually does
n8n's own node docs describe the setup. You create a second workflow whose first node is the Error Trigger. In the workflow you care about, you open the menu, choose Settings, and set Error workflow to that second workflow. When an automatic execution fails, n8n starts the error workflow and passes it the failed workflow's name, the error message, and, when the execution was saved, a link.
Two limits are in those same docs, and they are easy to miss. The Error Trigger does not run when you execute the main workflow by hand in the editor. Manual runs show their errors on the canvas, and n8n treats that as enough. And if the failure is in the trigger node itself, the payload is thinner: there is less under execution, because the workflow never really started, and more under trigger. Build the error workflow so a missing execution id does not crash the alert. A Slack node that assumes execution.url is always present will fail, and a failed error workflow does not page you either.
Keep the error workflow. It is the right tool for a node that throws, a credential that fails mid-run, or a Stop and Error node you use on purpose. It is the wrong tool for silence. Silence has no execution.
The runs that never become executions
- The workflow is not active. n8n only runs the error workflow for automatic executions of an active workflow. A workflow you deactivated, or never published, does not start and does not fail.
- The Schedule Trigger does not fire. The instance was down, the workflow timezone was wrong, or the process that owns the timer was not running. No execution is written.
- A polling trigger stops polling. The URL or credential it depends on is broken, and n8n records nothing because it never got as far as an execution. The error workflow is waiting for a failure that is never filed.
- A node is set to continue on error. The execution then ends as a success. The Error Trigger does not run, because from n8n's point of view nothing failed.
That last one is the 'green but did nothing' case inside a run that did start. The others are the case the title is about: the run did not start, so every in-n8n alert is structurally mute. The check has to be outside the workflow.
Put a heartbeat on the success path
Add an HTTP Request node as the last step on the path that means the work happened. Not beside the trigger. Not in the error workflow. At the end. A monitor is a promise about how often that request must arrive. You create the monitor with the same cadence as the workflow, plus a grace period for a slow API. The schedules page is the rule Nightshift uses: the next deadline is the next slot plus grace, and a missed deadline is the alert.
The HTTP Request node sends a JSON body when Send Body is on, Body Content Type is JSON, and Specify Body is Using JSON. Put the ping URL in a credential if you can. Many n8n instances block $env in expressions unless N8N_BLOCK_ENV_ACCESS_IN_NODE is false, so do not assume {{ $env.NIGHTSHIFT_PING_URL }} works. Do not commit a live token in a workflow export.
Method: POST
URL: https://nightshiftwatchdog.com/ping/YOUR_TOKEN
Send Body: on
Body Content Type: JSON
Specify Body: Using JSON
{
"items": {{ $json.items }}
}Nightshift's ping API stores three optional numbers: tokens, cost_usd, and duration_ms. Any other key, including items, is ignored. The request still succeeds, which is what resets the deadline. The count is there so the execution log shows what this run did. It does not, by itself, raise an alert when the count is zero. Read that again before you rely on the JSON. A successful POST with items set to 0 is a healthy heartbeat.
Catching a run that succeeded and moved nothing
n8n skips downstream nodes when the upstream node returns zero items. If your Sheets or database node finds nothing, the HTTP Request never runs, and the monitor eventually goes down. That is the right outcome when emptiness is the incident, and the wrong outcome when a quiet hour is normal. Decide which one you mean.
If a quiet hour is normal, the heartbeat has to run anyway. Turn on Always Output Data for the node that might return nothing, so the chain continues. That setting emits an empty item, which will inflate a naive count by one. Normalize it in a Code node that always returns a single item:
const rows = $input.all().filter((item) => Object.keys(item.json).length > 0);
return [{ json: { items: rows.length } }];Then branch. An IF node, value {{ $json.items }}, number, larger than, 0. The true branch is the success POST above. The false branch is the same request with /fail appended to the URL and no body. /fail marks the monitor down immediately instead of waiting for the grace period. Use it when zero items is the incident. Leave the false branch unconnected when zero items is fine, and let the success POST run on both sides so the heartbeat still lands.
There is a second empty-success trap. A node set to continue on error can hand the next node an error object and still let the workflow succeed. Do not put the heartbeat after a node that is allowed to fail open unless the IF checks the error output first. Otherwise you will ping success for a run that dropped the only record it was supposed to write.
Match the monitor to the schedule
Workflow settings, the same panel as the error workflow, has a timezone. The Schedule Trigger uses it. Set the zone on purpose rather than inheriting whatever the instance default is. Then create the monitor in that same zone, with the same five-field cron, and a grace period longer than the workflow's worst normal run. A workflow that starts at 07:00 and sometimes finishes at 07:06 should not have a one-minute grace. A workflow you expect hourly should not be watched with a five-minute interval, or you will page yourself every hour.
Ping /fail from the error workflow too, if you want a crash to page you before the deadline. That POST is in addition to the success heartbeat, not a replacement. A crash still produces an execution. A workflow that was never started does not, and only the missing success ping can say so.
Nightshift Watchdog is the monitor on the other end of that URL. The free plan covers 3 monitors and emails you when a ping does not arrive, and again when it recovers. Setup is the HTTP Request node above. The docs cover the ping fields, grace periods, and where /start differs from a finished run.