# n8n workflow health watchdog: alert when a scheduled workflow stops succeeding

## The problem

A scheduled n8n workflow can stop doing useful work while its `active` flag stays `true`, so checking `active` (as in the [forum thread](https://community.n8n.io/t/326358) this came from) tells you nothing about health. The editor's red or green dot comes from an internal session-authenticated route (`/rest/...`) that an API key cannot call, so you need a signal you can get from the public API.

## How the fix works

For each workflow id, the watchdog asks the public API (`/api/v1`, header `X-N8N-API-KEY`) three things:

1. `GET /api/v1/workflows/{id}`: is `active` true?
2. `GET /api/v1/executions?workflowId={id}&status=success&limit=1`: when did it last succeed?
3. `GET /api/v1/executions?workflowId={id}&status=error&limit=5`: has it failed since?

Then it gives one verdict, checked in this order:

| Verdict | Meaning |
|---|---|
| `INACTIVE` | `active` is false |
| `STALE` | no successful execution found, or the newest one finished more than `maxSilenceMinutes` ago |
| `ERRORING` | the newest error execution started after the newest success (it may still be inside the silence limit) |
| `OK` | none of the above |

"Minutes since success" uses `stoppedAt` of the newest success, or `startedAt` if `stoppedAt` is missing. The newest item is picked by `startedAt`, not by trusting the order of the list. Anything that is not `OK` sends one short message to your chat webhook.

Two implementations of the same logic are included:

- `watchdog.n8n.json`: an n8n workflow. Import it and fill in the config node.
- `watchdog.py`: a standalone Python 3 script (standard library only). Use it from cron or a systemd timer, ideally on a machine other than the one running n8n.

## Files

| File | Purpose |
|---|---|
| `watchdog.n8n.json` | n8n workflow: Schedule (30 min), Config, Split IDs, 3 HTTP calls, Verdict, IF not OK, alert, optional reactivate |
| `watchdog.py` | Same logic as a script. Prints a table, exits 1 if anything is not OK |
| `sample/` | Fake API responses ("Sample Co") for the offline test |
| `test_fix.py` | Offline test |

## Setup (n8n workflow)

1. In n8n, go to Settings, then n8n API, and create an API key. Keep it read-only if your version lets you choose scopes. Reactivation needs write access to workflows, so only grant that if you want `autoReactivate`. Check the current docs for which scopes the workflow and execution endpoints need.
2. Create a credential: Credentials, New, type **Header Auth**. Name the credential `n8n API key header`, set **Name** to `X-N8N-API-KEY` and **Value** to your key.
3. Find the id of each workflow to watch. It is in the editor URL: `/workflow/<id>`.
4. Create an incoming webhook in your chat tool (Slack, Discord or RocketChat) and copy its URL.
5. In n8n choose Import from file and select `watchdog.n8n.json`.
6. Open the **Config** node and set:
   - `n8nBaseUrl`: for example `http://localhost:5678`, with no `/api/v1` and no trailing slash needed.
   - `workflowIds`: comma separated, for example `abc123,def456`.
   - `maxSilenceMinutes`: default 30. Pick roughly three times the schedule interval of the watched workflow.
   - `alertWebhookUrl`: your webhook URL. Treat it like a password and remove it before sharing the workflow export.
   - `alertField`: `text` for Slack and RocketChat, `content` for Discord.
   - `autoReactivate`: leave `false` unless you want step 9.
7. Open each of the four HTTP Request nodes that carry a credential (**Get workflow**, **Last success**, **Recent errors**, **Activate workflow**) and select `n8n API key header`. The imported file only holds a placeholder id.
8. Click Execute workflow once. Check that **Verdict** shows one item per workflow id, then activate the workflow.
9. Optional: set `autoReactivate` to `true`. For INACTIVE workflows the watchdog then calls `POST /api/v1/workflows/{id}/activate`. Check the current docs for your n8n version, since publishing behaviour has changed between releases and I could not test this against a live instance.

Timing: the schedule is every 30 minutes, so with `maxSilenceMinutes` of 30 a stall can take up to about an hour to be reported. Shorten the schedule in the first node if that is too slow.

## Setup (Python script)

```
export N8N_API_KEY='...'                      # never put the key on the command line
export ALERT_WEBHOOK_URL='https://hooks.example.com/...'   # optional
python3 watchdog.py --base-url http://localhost:5678 \
  --workflow-id abc123 --workflow-id def456 --max-silence-min 30
```

Without `--apply` it only prints. Add `--apply` to actually send alerts, and add `--auto-reactivate` as well (with `--apply`) to call the activate endpoint. Other flags: `--alert-webhook`, `--alert-field` (`text` or `content`), `--api-prefix` (default `/api/v1`, change it if the docs for your version differ). Exit codes: 0 all OK, 1 something is not OK, 2 the API could not be queried or a key is missing.

Example cron line, every 30 minutes:

```
*/30 * * * * N8N_API_KEY=... ALERT_WEBHOOK_URL=... /usr/bin/python3 /path/watchdog.py --workflow-id abc123 --apply
```

## Test it offline

```
python3 test_fix.py
```

This needs no n8n, no network and no keys. You can also run the script on the fixtures:

```
python3 watchdog.py --mock sample --workflow-id wf-healthy --workflow-id wf-stale --workflow-id wf-inactive
```

Expected: one `OK`, one `STALE` (last success 180 minutes before the fixed sample time), one `INACTIVE`, and exit code 1. In mock mode only the two HTTP functions are swapped for fixture lookups. All other code is the same as in live mode. If `node` is installed, the test also runs the Verdict code from the workflow JSON on the same fixtures and checks it gives the same messages as the Python.

## Limits, and what is not covered

- **Not tested against a live n8n.** The endpoints and response fields used (`active`, `startedAt`, `stoppedAt`, `status`, `data[]`) are from the public API as I know it. The offline test checks logic only. Check the current API docs for your version before relying on it.
- **If n8n does not store successful executions, every workflow looks STALE.** Check your workflow settings for saving successful production executions, and your execution pruning settings. Pruning old executions can also hide an older success.
- **A watchdog on the same sleeping laptop sleeps too.** If the machine suspends, the n8n watchdog stops with it. Run `watchdog.py` from another always-on machine if sleep is your main cause.
- **It does not reproduce the editor dot.** It does not report trigger activation errors or unpublished draft edits, which the thread says the editor dot also reflects. It judges health only by recent executions.
- **Slow or rare schedules.** For a workflow that runs daily, set `maxSilenceMinutes` accordingly. One value applies to all ids in a run, so split different cadences into separate runs.
- **Error count is capped.** It looks at the 5 newest error executions, so "recent errors" never exceeds 5.
- **The watchdog itself can fail.** If the API returns 401 the n8n workflow errors and the script exits 2. Attach an Error Workflow to the watchdog or monitor the cron exit code.
- **Reactivation is blunt.** It only turns the workflow back on. It does not fix a bad mail or LLM node.
