When an API Renames a Field: How n8n Workflows Write Empty Records Without an Error
31 Oct 2026
Some of the quietest failures in n8n don't involve a dead trigger or an expired token. The workflow runs, every node is green, the CRM gets a new record on schedule, and the record is wrong. The email field is blank. The lead source says "undefined". The deal value is zero.
Usually the cause is a field that changed name or shape upstream. An API returns email_address where it used to return email. A form tool nests answers under a new key. An AI node that used to output {"company": ...} now outputs {"company_name": ...} because someone tweaked the prompt. n8n keeps going, because from its point of view nothing failed.
This post is about that one problem: schema drift that breaks downstream nodes without an error, including AI-output-to-CRM chains. It's related to the zero-item success described in Green doesn't mean it worked, but harder to spot, because items still flow.
The symptom
- Executions succeed at the normal rate. The error workflow never fires.
- Records keep arriving downstream, but one or more fields are empty, contain the literal text
undefinedor[object Object], or hold a default value. - Filters and IF nodes start routing everything down one branch. For example, a lead score check that reads a missing field sends every lead to "cold".
- Deduplication breaks. If the key you match on is suddenly empty, an upsert can create duplicates or overwrite the same record repeatedly.
- It starts on a specific day: the day the vendor shipped an API change, the form was edited, or the prompt was changed.
Why it happens
Expressions don't fail on missing fields
In n8n, mapping a value usually means an expression such as {{ $json.email }} or {{ $('Webhook').item.json.body.email }}. If the field doesn't exist on the item, the expression doesn't throw. It resolves to an empty or undefined value, and the node that receives it carries on. Many app nodes, including most CRM nodes, accept an empty value for optional fields. The result is a record that was "successfully" created with nothing in it.
A required field is the lucky case: the destination API rejects the request and you get a real error. The unlucky cases are optional fields, fields used only in logic (IF, Switch, Filter), and fields that get a default.
Vendors change payloads
APIs and webhooks change for ordinary reasons: a new API version, a renamed form question, a field moved into a nested object, a number that's now sent as a string, an array that used to have one element and now has two. Webhook payloads are especially exposed, because the shape is often defined by whoever built the form, not by a versioned API.
AI nodes produce a different shape from run to run
AI-output-to-CRM chains add a source of drift that doesn't need any vendor change. A language model asked to "return JSON with the company, contact name and intent" will usually comply, but the key names, nesting and types can vary between runs, prompts and model versions. Common patterns:
- A key is renamed (
companyvscompany_name) or capitalised differently. - The JSON arrives as a string inside the output field rather than as an object, so
$json.output.companyis undefined. - The model wraps the JSON in prose or a code fence.
- A field the prompt asked for is simply missing when the model wasn't sure.
"Always Output Data" and continue-on-error hide it further
n8n's Always Output Data node setting makes a node return an empty item even if it produced no data (n8n docs: node settings). That's handy for keeping a branch alive, but it also means an empty item can travel all the way to the CRM node. Similarly, an On Error setting of continue turns a failure into an item that downstream nodes may treat as data.
How to detect it by hand
The general fix is the same as for any silent failure: decide what a correct item looks like, check it at the boundary, and make bad items fail loudly. Here's how to do that in n8n.
1. Validate at the point where data enters
Put a guard right after the trigger or the HTTP Request node that fetches external data, before any mapping:
- Add an IF node with one condition per required field, using the "is empty" or "does not exist" operators on each field, combined with OR.
- Route the true branch (something missing) to a Stop and Error node. Give it a message that names the workflow and the missing field. Stop and Error makes the execution fail on purpose, so your error workflow runs (n8n docs: Handle errors gracefully).
- Route the false branch (all present) into the rest of the workflow.
If you have many fields, a Code node is easier to maintain. A compact version:
const required = ['email', 'first_name', 'source'];for (const item of $input.all()) {const missing = required.filter(k => item.json[k] === undefined || item.json[k] === null || item.json[k] === '');if (missing.length) throw new Error('Missing fields: ' + missing.join(', ') + ' in ' + JSON.stringify(item.json).slice(0, 300));}return $input.all();
Throwing in the Code node fails the execution, which reaches your error workflow, and includes the start of the payload so you can see what the new shape looks like.
2. Don't let one rename poison logic
For fields used in IF, Switch or Filter nodes, decide what should happen when the field is missing. Usually that's "fail", not "take the false branch". Put the existence check before the business logic, so a missing lead_score doesn't quietly route every lead to the cold path.
3. Force a schema on AI output
For AI agent or LLM chains that feed a CRM, give the model's output a schema and check it:
- Enable Require Specific Output Format on the AI node (where available) and attach a Structured Output Parser. The parser returns fields based on a JSON Schema you define (n8n docs: Structured Output Parser).
- Define the schema with explicit property names, types and a
requiredlist. For example, an object withcompany(string),contact_email(string) andintent(one of a fixed set of values), all required. - After the AI node, still run the same IF or Code guard from step 1 on the parsed fields. A parser reduces drift; it doesn't remove the need to check what reaches the CRM.
- Map the CRM fields from the parsed output (for example
{{ $json.output.contact_email }}), not from free text. - When you change the prompt or the model, rerun a few saved test inputs and compare the output keys before publishing.
4. Check what arrived, not just what was sent
The upstream guard catches missing input. It can't catch a mapping that points at the wrong field. Two cheap checks downstream:
- Read back after write. After the CRM create or update node, fetch the record (or use the node's output) and check that the key fields are populated. If not, Stop and Error.
- A daily data-quality query. A scheduled workflow that counts yesterday's new records with an empty email, a blank source or a duplicate key, and alerts if the count is above zero. Most CRMs and sheets make this a single search.
5. Count good records, not runs
When you report success to anything (a log sheet, a heartbeat, a client report), count only the items that passed validation. If 40 items came in and all 40 had an empty email, the useful number is zero, not 40. That turns a silent data-quality failure into a visible zero.
6. When you find one, compare executions
Open a failing execution and a known-good execution from before the change side by side. In the trigger or HTTP Request node, compare the JSON output. The renamed or moved field is usually obvious. n8n lets you load data from a previous execution into the editor to debug against it (n8n docs: Handle errors gracefully).
How LeadPulse catches it
LeadPulse doesn't inspect the content of your CRM records, so it won't notice by itself that an email field is blank. What it does is turn the checks above into alerts, and catch the cases where a schema change shows up in the numbers:
- If your validation guard fails the execution with Stop and Error or a thrown Code error, the workflow's error rate rises and LeadPulse alerts, once, with a recovery note when it's fixed.
- If a field change makes a filter drop everything, the run succeeds with no items. LeadPulse's zero records on a green run rule catches that, and the drop against the trailing 7-day pace rule catches the partial version, when output falls well below normal.
- If you use a heartbeat at the end of the workflow, send the number of valid records as
count. A run that arrives on time withcount=0raises a zero-count alert, separate from a missed heartbeat.
The checker runs every 10 minutes. Email alerts come on every plan, and Slack, Telegram and webhook alerts on the trial and paid plans. On Agency and Pro, incidents also appear in the white-label Monday report for each client.
Find the quiet ones first
Run the free n8n Health Check on your instance. It reads your workflows and recent executions once with an API key (read-only is enough), scores them with the same rules the monitor uses, and discards the key. No account is needed, and you enter your email to see the results. Then add a validation guard to every workflow that writes to a CRM, and send the valid-record count somewhere you'll see it. The Free plan covers 3 monitored workflows with email alerts if you want that alerting without building it yourself.
Sources
- n8n docs: Node settings (Always Output Data, Retry On Fail, On Error)
- n8n docs: Handle errors gracefully (error workflows, Stop and Error)
- n8n docs: IF node
- n8n docs: Structured Output Parser
- Make Help Center: Fix missing data errors (the same problem on Make: BundleValidationError, ifempty)
- LeadPulse: n8n heartbeat monitoring
- LeadPulse: homepage (outcome rules)