Validating incoming JSON payloads in n8n: schema, Ajv and best practices
By Clément Lacaille (Tech-Bharat) · Published 21 September 2026 · 6 min read · how we build and test
Complete FlowKit Bundle — €269
12 ready-to-import n8n workflows, setup guide in French, 14-day guarantee
An n8n Webhook node accepts any valid JSON, regardless of its structure. Send {"email": "test"} to a workflow that expects {"email": "...", "amount": 42}, and nothing stops at the entry point: the workflow keeps going until some node further down — an LLM call, a Supabase write, an email send — fails on a missing value or the wrong type. By then you've already paid for the model call and lost track of what was actually sent. Validating the shape of the data right at the entry point, before any processing, costs a few lines and avoids this kind of delayed failure.
Why an "almost right" payload breaks a workflow further downstream
Malformed JSON almost never triggers an immediate error at the entry point of an n8n workflow. The Webhook node receives the request, turns it into an item, and passes it along as-is — missing field, unexpected type, or null value included. The error only shows up at the node that actually tries to use that data: a $json.amount.toFixed(2) that crashes because amount is a string, an LLM prompt that receives undefined and hallucinates a plausible-sounding answer about missing data, a Supabase row inserted with an empty required field that breaks a constraint further down the pipeline.
This gap between the faulty input and the visible error isn't unique to n8n — it's a classic of application security. A landmark study by Scholte, Balzarotti and Kirda, published in Computers & Security in 2012, analyzed several thousand real-world web vulnerabilities and found that the vast majority don't come from a complex sanitization failure, but from the plain absence of upstream validation (Scholte, Balzarotti & Kirda, 2012). The same pattern carries over directly to an automation workflow: the failure is almost never in the business logic itself, but in the implicit assumption that whatever data enters it is already correct.
Authentication is not validation
It's tempting to assume a webhook protected by Header Auth or an HMAC signature is a safe webhook. Authentication answers exactly one question: who is allowed to call this endpoint. It says nothing about the shape of what's sent. A perfectly legitimate partner, with the right API key, can easily post incomplete JSON because of a bug on their end, a field renamed without notice, or a frontend form that lets an empty field through. Structural validation is an independent layer, complementary to authentication and to deduplication via idempotency: each answers a different question about the incoming request.
The native approach: validating with zero dependencies
For most workflows, a simple schema (a handful of required fields, a few types to check) is handled perfectly well with n8n's native nodes, without touching server configuration — so it works equally well on n8n Cloud and self-hosted.
Place an IF or Filter node right after the Webhook, with an expression that checks each constraint:
{{
$json.body.email &&
$json.body.email.includes('@') &&
typeof $json.body.amount === 'number' &&
$json.body.amount > 0
}}
The "false" branch goes to a Respond to Webhook node that returns an explicit 400 instead of letting the workflow continue on invalid data:
{
"error": "invalid_payload",
"details": "email is required and amount must be a positive number"
}
This approach has a clear limit: it quickly becomes unreadable once a schema goes past three or four fields, or needs to validate nested structures (an array of objects, conditional fields). That's where an explicit schema takes over.
Going further: a real JSON Schema with Ajv
Ajv is the reference library for validating JSON against a schema (the JSON Schema specification) in JavaScript. It runs inside a Code node, provided your instance is self-hosted: like any external npm module, it has to be physically installed and allow-listed via NODE_FUNCTION_ALLOW_EXTERNAL — the full procedure is covered in our guide on installing npm modules in the n8n Code node.
const Ajv = require('ajv');
const ajv = new Ajv({ allErrors: true });
const schema = {
type: 'object',
required: ['email', 'amount'],
properties: {
email: { type: 'string', format: 'email' },
amount: { type: 'number', minimum: 0 },
currency: { type: 'string', enum: ['EUR', 'USD'] },
},
additionalProperties: false,
};
const validate = ajv.compile(schema);
const valid = validate($json.body);
if (!valid) {
throw new Error(JSON.stringify(validate.errors));
}
return $input.all();
Ajv handles constraints that native expressions don't cover comfortably: formats (email, date-time, uri), enums, numeric bounds, typed arrays, oneOf/anyOf for conditional fields, and additionalProperties: false to reject any unexpected field instead of silently ignoring it. The schema also doubles as executable documentation of the contract the workflow expects — useful for whoever picks it back up six months from now.
On n8n Cloud: stick with the native approach
n8n Cloud lets you install verified community nodes from the nodes panel (Starter plan and above), and a few exist for JSON Schema validation. But a community node is still third-party code running on your instance: reserve that option for nodes actually marked "verified" by n8n, and for a schema that stays reasonably simple, native IF/Filter nodes avoid adding an external dependency for a marginal gain.
Where to put validation in the workflow
The validation node should be the very first one after the trigger — before any API call, any LLM call, any database write. It's the same logic as rate-limiting a webhook that calls an LLM: blocking early costs a string comparison, blocking late costs an OpenAI or Anthropic call billed for nothing. Wire the "invalid" branch to a dedicated error workflow rather than letting the exception bubble up raw: a 400 detailing which fields are wrong lets the caller fix their integration, while a generic 500 gives them nothing to act on.
A concrete case: a lead-capture webhook
A contact form posting to n8n typically sends { "name": "...", "email": "...", "message": "...", "consent": true }. Without validation, a missing consent field can end up stored as undefined in Supabase — a GDPR problem, not just a technical one, since proof of consent needs to be traceable (see our guide on the GDPR audit trail). An Ajv schema with consent: { type: 'boolean', const: true } as a required field rejects the request before anything gets stored if the checkbox wasn't ticked, instead of discovering the gap months later during an audit.
The same principle applies to the POST /ask API of the RAG Assistant Pack (€119): validating that question is a non-empty string before querying the vector store and the LLM avoids billing a model call for an empty payload sent by mistake — a direct complement to the authentication covered in our guide on securing an n8n webhook.
The mistakes that cost you
- Trusting the type you assume you're receiving: a numeric field sent as a string by a frontend form goes unnoticed until a calculation fails three nodes later.
- Validating after the LLM call instead of before: node order matters as much as the validation itself.
- Missing
additionalPropertiesin an Ajv schema: an unexpected field (sometimes an injection attempt) sails through the workflow, neither rejected nor flagged. - A generic error message: returning a
400without specifying which field is at fault turns every failed integration attempt into blind debugging for the caller. - Conflating authentication with validation: both are necessary, and neither replaces the other.
Wrapping up
A malformed JSON payload never stops on its own at the entry point of an n8n workflow — it keeps going until it breaks something less predictable, often after already consuming a paid resource. For a simple schema, IF or Filter nodes are enough, with no dependency and no server configuration. For a richer data contract — conditional fields, strict formats, nested structures — Ajv in a Code node on a self-hosted instance covers the rest, with a schema that documents as much as it validates. Either way, the rule doesn't change: validate early, before the error costs more than a comparison.
FAQ
Frequently asked questions
Do I still need to validate a payload if the webhook is already authenticated?
Yes — they solve two different problems. Authentication (Header Auth, HMAC) checks who is allowed to call the webhook. Validation checks what that caller actually sends. A legitimate caller can perfectly well post malformed JSON — a missing field, an empty email, a number sent as text — and authentication catches none of that.
Does Ajv work on n8n Cloud?
Not directly inside a Code node: Ajv is an external npm module, and n8n Cloud doesn't let you add arbitrary packages to the Code node. On n8n Cloud, the native approach with IF or Filter nodes remains the most reliable option; self-hosted instances can unlock Ajv via NODE_FUNCTION_ALLOW_EXTERNAL.
How is this different from the Structured Output Parser node?
The Structured Output Parser constrains what an LLM outputs to match a given format — it validates what the AI produces. The schema validation described here applies to what enters the workflow before any processing: a webhook payload, a form, an uploaded file. The two are complementary rather than redundant.