How I Fixed the "Empty Object" Bug in n8n (and Hit 99.7% Reliability)
TL;DR:
- Webhooks often send empty objects (
{}) for cancellations, which JavaScript treats as "truthy." - Standard array length checks fail when n8n returns
[{}]. - A 3-layer validation system (Structure, ID, and Data) prevents blank emails from reaching customers.
Prerequisites
- Basic knowledge of n8n workflows.
- Understanding of JavaScript truthy/falsy values.
- Experience handling webhooks from tools like Cal.com or Typeform.
The Problem: The Silent Killer of Automations
I recently faced a nightmare scenario while building a lead management system for the Aviators Training Centre. 40% of my booking confirmation emails were sending with blank data.
No name. No meeting time. Just empty fields.
Users received emails saying: "Hi , your meeting is scheduled for " with awkward blank spaces where their information should be. It looked unprofessional and risked losing potential students.
After deep-diving into the n8n execution logs, I found the culprit. Cal.com sends webhooks for both bookings and cancellations. When a cancellation happened, the payload was an empty object: {}.
Because n8n has alwaysOutputData: true enabled by default in many nodes, these empty objects passed through my filters. In JavaScript, an empty object is technically truthy, so my "if data exists" checks were waving them through to the email node.
What I Tried First (That Failed)
Before I found the solution, I went through several failed attempts that I'm sure many of you have tried too:
Attempt 1: Checking array length
if (bookingData.length > 0) return bookingData;
Why it failed: n8n often wraps the payload in an array. An array containing an empty object [{}] still has a length of 1.
Attempt 2: The simple truthy check
if (bookingData[0]) return bookingData;
Why it failed: As mentioned, {} is truthy. The check passes, but there is no usable data inside.
Attempt 3: Checking for a specific field
if (bookingData[0].id) return bookingData;
Why it failed: If the id field is missing entirely, this throws a null reference error and crashes the whole workflow execution.
The Solution: 3-Layer Validation Architecture
To solve this, I built a multi-layer validation system. This architecture ensures that only "healthy" data makes it to the final stages of the workflow.
Layer 1: Array Structure Check
First, we verify if we even have a list of data to look at. This catches completely empty responses or null values.
Layer 2: ID Field Validation
Next, we check if a unique identifier exists. Since I was using Airtable and Cal.com, I looked for an ID that followed a specific format (like starting with 'rec').
Layer 3: Meaningful Data Check
Finally, we ensure the object has more than just one or two keys. If an object only has an ID but no name or email, it's still useless for a confirmation email.
Implementing the Full Validation Function
Here is the exact code I used in a Code Node to filter out the noise. This function uses "semantic indicators" - if no valid data is found, it returns a specific flag (_noLeadsFound) that downstream nodes can easily recognize.
// src/utils/validation.js
function isValidLead(item) {
// Layer 1: Check if the item and json property exist
if (!item || !item.json) return false;
const data = item.json;
// Layer 2: Check ID exists and matches expected format
if (!data.id || !data.id.startsWith('rec')) return false;
// Layer 3: Check for required fields (Name, Email, Start Time)
if (!data.name || !data.email || !data.startTime) return false;
// Final Check: Ensure it's not just an object with an ID
if (Object.keys(data).length <= 1) return false;
return true;
}
const validLeads = items.filter(isValidLead);
if (validLeads.length === 0) {
// Return a semantic indicator instead of an empty array
return [{ json: { _noLeadsFound: true } }];
}
return validLeads;
The "Smart Indicator" Pattern
One of the biggest lessons I learned was using the _noLeadsFound flag. Instead of letting the workflow stop or error out, I pass this flag to an IF Node later in the flow:
// IF Node Condition
$input.first().json &&
!$input.first().json._noLeadsFound &&
$input.first().json.id
This prevents the "Empty Object" from ever reaching the email template, while still allowing the workflow to finish gracefully.
The Results: 99.7% Reliability
After deploying this 3-layer architecture at the Aviators Training Centre, the results were immediate.
- Reliability: Jumped from 60% to 99.7%.
- Blank Emails: Dropped to 0%.
- Production Stats: Over 42 consecutive checks passed across three different triggers (Cal.com, Firebase, and Booking Management) with zero errors.
I no longer have to spend hours debugging execution logs to find out why a customer received a broken email. The system handles the edge cases automatically.
Key Takeaways
- Never trust webhook data: Always assume the payload might be empty or malformed.
-
Objects are tricky: Remember that
[{}]is truthy and has a length of 1. -
Use Semantic Indicators: Passing a flag like
_noLeadsFoundis much cleaner than trying to handle nulls in every single node. - Validate in layers: Catch structure issues first, then ID issues, then data completeness.
Bookmark this pattern for the next time you build a customer-facing automation. It will save you from a lot of embarrassing "Hi [Blank]" emails.
What's your approach?
I used a custom Code Node for this validation, but I have seen people use complex chains of Filter nodes to achieve something similar. Which do you prefer: writing a bit of JavaScript or keeping it strictly no-code with more nodes?
I'm documenting my entire build-in-public journey here on Dev.to. If you want more practical n8n and Next.js patterns, hit the follow button.
Let's connect:


Top comments (1)
The empty object truthy trap is one of those JavaScript gotchas that bites everyone at least once. I run a bunch of webhook-driven automations on a home server and hit the exact same issue with Cal.com cancellation events sending
{}payloads. Your 3-layer validation approach is solid — I ended up doing something similar where I check for both the existence of the key AND that the value is not an empty string, because some APIs send{"name": ""}which passes object key checks but still results in blank emails.One thing that helped me was adding a dead letter queue for any payload that fails validation, so I can inspect what went wrong later instead of silently dropping it. Saved me hours of debugging.