Validating workflow output: empty results and invalid JSON
Treat empty or malformed results as failures. Check for emptiness, valid JSON and required keys on every run.
Most automation monitoring asks one question: did the run finish? That misses a large share of real incidents. The run finished, the dashboard is green, and the output was an empty array, a null field, or an HTML error page where JSON should be. Here's how to treat bad output as a failure, with checks you can set up in a few minutes.
Where empty and invalid output comes from
- Upstream APIs returning success with nothing in it. A search with an expired filter, a paginated endpoint returning page 0 of 0, a report API that answers
{"data": []}while the warehouse load is late. - Auth and proxy errors returned as
200. Some services return an HTML login or maintenance page with a 200 status. The next step tries to parse it, or worse, stores it. - Mapping drift. A field gets renamed upstream (
email→emailAddress), and your mapping now produces empty strings for every record. - "Continue on error" settings. In n8n, On Error: Continue and Always Output Data keep a workflow going after a failed node, which is sometimes what you want, but the final output may be empty.
- LLM output. Truncated responses, JSON wrapped in Markdown fences, refusals. See the AI agent guide.
None of these produce a failed execution. You need a check on the data itself.
Three levels of checking
- Not empty. The output has some non-whitespace content. This catches blank responses and empty fields sent as the whole result.
- Valid JSON. The output parses as JSON. This catches truncation, HTML error pages and code-fenced text.
- Required keys. Specific fields exist and have a value. This catches empty arrays,
nulls and renamed fields.
You can do these inside the workflow with IF nodes or filters, and for critical steps you should. Doing them at the monitor as well has two advantages. The checks live in one place for all your workflows. And a failed check produces the same alert, with the same recovery notification, as a run that didn't happen at all.
Step 1: set the rules on a monitor
In Flowhush, create or edit a monitor and open Output checks (Pro and Team):
[x] Body must not be empty
[x] Body must be valid JSON
Required JSON keys (comma separated, dot paths ok): data.items, data.items.0.id, meta.generated_at
How the rules are evaluated, from the implementation:
| Body sent | Rules | Result |
|---|---|---|
| (empty body) | not empty | Fails: Output is empty |
<html>Service Unavailable</html> | valid JSON | Fails: Output is not valid JSON |
{"data": {"items": []}} | key data.items | Fails: Missing or empty JSON key: data.items |
{"data": {"items": [{"id": ""}]}} | key data.items.0.id | Fails: empty string |
{"count": 0} | key count | Passes: 0 is a value |
{"ok": false} | key ok | Passes: false is a value |
[{"id": 7}] | key 0.id | Passes: top-level arrays work |
Details worth knowing:
- A key counts as missing if it's absent,
null, an empty or whitespace-only string,[]or{}. Numbers and booleans always count as present, so check values like "count must be above zero" in the workflow itself. - Required keys imply valid JSON, and you can set up to 20.
- Validation applies to success pings sent with
POSTorPUT. A plainGETping has no body, so on a monitor with rules it fails as empty. Switch your ping step to POST when you add rules. - Up to 64 KB of the body is read. A larger JSON body gets cut off and fails as invalid, so send a summary rather than the full dataset.
- A failed check returns HTTP
200withOK (recorded as failure: …), so the workflow itself doesn't error. The monitor goes down and you get one alert with that reason. The first 1 KB of the body is kept in Recent pings.
Step 2: send the output
n8n
Put an Edit Fields (Set) node before the ping if you only want to send a summary, then an HTTP Request node:
Method: POST
URL: https://flowhush.com/p/YOUR-MONITOR-UUID
Send Body: on
Body Content Type: JSON
Specify Body: Using JSON
JSON: {{ JSON.stringify($json) }}
Settings → Execute Once: on
Settings → On Error: Continue
With Execute Once on, $json is the first item. To check that the whole run produced items, send all of them as an array and require 0.id:
JSON: {{ JSON.stringify($input.all().map(item => item.json)) }}
If the previous node can output nothing at all, n8n stops the branch there and the ping never runs. That's also a failure, caught by the deadline. To have it reported immediately instead, turn on Always Output Data on that node so the ping still runs, and the empty body fails the check.
Make
Add HTTP → Make a request as the last module:
URL: https://flowhush.com/p/YOUR-MONITOR-UUID
Method: POST
Body content type: application/JSON
Body input method: JSON string
Body: (see below)
Building JSON by hand in a text field breaks as soon as a mapped value contains a quote or a newline. Use the JSON → Create JSON module first, with a simple data structure (items_count, first_id, generated_at), and map its output into the body.
Any script
# jq builds valid JSON safely; curl posts it
jq -n --argjson items "$(cat items.json)" '{data: {items: $items}, meta: {generated_at: now|todate}}' |
curl -fsS -m 10 --retry 3 -X POST -H 'Content-Type: application/json' \
--data-binary @- https://flowhush.com/p/YOUR-MONITOR-UUID
Because a failed check still returns 200, curl -f won't exit non-zero. If a script needs to know, check the response text:
resp=$(curl -sS -m 10 -X POST -H 'Content-Type: application/json' \
--data-binary @result.json https://flowhush.com/p/YOUR-MONITOR-UUID)
case "$resp" in "OK (recorded as failure"*) echo "output check failed: $resp" >&2 ;; esac
Step 3: test each rule
U=https://flowhush.com/p/YOUR-MONITOR-UUID
curl -sS -X POST --data-binary '' $U # OK (recorded as failure: Output is empty)
curl -sS -X POST --data-binary '<html>' $U # OK (recorded as failure: Output is not valid JSON)
curl -sS -X POST -d '{"data":{"items":[]}}' $U # OK (recorded as failure: Missing or empty JSON key: data.items)
curl -sS -X POST -d '{"data":{"items":[{"id":1}]},"meta":{"generated_at":"2026-11-03"}}' $U # OK
The first failing ping sends the down alert, and the last one sends the recovery. Flowhush alerts once per incident, so repeated failures don't flood your channel.
Choosing what to require
Require the fields a downstream person or system depends on, not every field. Good candidates: the record ID you write to, the list you iterate over, a timestamp that proves the data is fresh, and a total or summary. If a list can legitimately be empty on some runs, such as "no new orders overnight", don't require it. Send a count instead and check the business rule in the workflow.
Flowhush's Free plan includes 5 heartbeat monitors. Output checks (not empty, valid JSON, required keys) are on Pro at $12/month, or $120/year.
Monitor your first workflows free
The Free plan includes 5 heartbeat monitors with alerts to email, Slack, Discord or Telegram. No credit card required.
Start free — 5 monitors See setup snippetsSources
- n8n docs: HTTP Request node
- n8n docs: Node settings (Always Output Data, Execute Once)
- Make Apps: HTTP
- RFC 8259: The JSON Data Interchange Format
Third-party features and prices were checked on 3 November 2026 and may have changed since.