Schema Validator Node
The Schema Validator checks the event payload against a set of field definitions you build on screen. A payload that fails stops the pipeline right there — nothing else in the flow is dispatched — and on a webhook in Sync response mode it is also what answers the caller.
Overview
Schema Validators act as structural gatekeepers in your pipeline. They inspect the incoming event payload against a set of field definitions, checking that required fields are present, values match expected types, and constraints (min, max, pattern, etc.) are satisfied. When a payload fails validation the pipeline stops there: neither the validator's own outputs nor any other node in the flow is dispatched. There is no “invalid” branch — put a Conditional in front of the validator if you need one.
targetUrl is queued when the event arrives, before any validator runs, and a rejection does not cancel it. To put a schema in front of an outbound call, leave targetUrl empty and deliver with an HTTP Action placed after the validator.In strictMode, any fields present in the payload that are NOT defined in the schema will also cause validation to fail. This prevents unexpected data from passing through. On the canvas, Schema Validator nodes appear in cyan (#06b6d4) with an ID prefix of sv-.
Configuration
| Field | Type | Default | Description |
|---|---|---|---|
| name | string | — | Friendly label for the schema validator |
| mode | validate | reply | validate | What the node does when the run reaches it. validate checks the payload against fields; reply checks nothing and answers the caller with reply instead (see Reply only mode below) |
| reply | { status?, body? } | null | null | The answer a Reply-only validator gives: an HTTP status from 200 to 599 and a JSON body whose text values are {{payload.…}} templates. Ignored while mode is validate |
| strictMode | boolean | false | When true, rejects payloads with fields not defined in the schema |
| fields | SchemaField[] | [] | Array of field definitions (see below) |
| inputNodes | { nodeType, nodeId }[] | [] | Upstream nodes that feed this one. nodeType is the node kind (webhook, scheduledWorkflow, filter, …) |
| isActive | boolean | true | Whether the validator is enabled |
SchemaField object
| Field | Type | Description |
|---|---|---|
| path | string | Dot-notation path into the payload (e.g. data.object.amount) |
| type | string | Expected type: string, number, boolean, object, or array |
| required | boolean | Whether the field must be present in the payload |
| constraints | object | Optional constraints object (see constraints table) |
Constraints
| Constraint | Applies to | Description |
|---|---|---|
| min | number | Minimum numeric value |
| max | number | Maximum numeric value |
| minLength | string, array | Minimum length / element count |
| maxLength | string, array | Maximum length / element count |
| pattern | string | Regular expression the value must match |
| enum | string, number | Array of allowed values |
Create a Schema Validator
curl -X POST /api/schema-validators \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"workspaceId": "<your-workspace-id>",
"name": "Payment Event Schema",
"strictMode": false,
"fields": [
{
"path": "type",
"type": "string",
"required": true,
"constraints": {
"enum": ["payment_intent.succeeded", "payment_intent.payment_failed", "charge.succeeded"]
}
},
{
"path": "data.object.amount",
"type": "number",
"required": true,
"constraints": {
"min": 0,
"max": 99999999
}
},
{
"path": "data.object.currency",
"type": "string",
"required": true,
"constraints": {
"minLength": 3,
"maxLength": 3,
"pattern": "^[a-z]{3}$"
}
},
{
"path": "data.object.customer",
"type": "string",
"required": false
},
{
"path": "data.object.metadata",
"type": "object",
"required": false
}
],
"inputNodes": [{ "nodeType": "webhook", "nodeId": "6654a1b2c3d4e5f6a7b8c9d0" }],
"isActive": true
}'Response
{
"_id": "6655a7b8c9d0e1f2a3b4c5d6",
"name": "Payment Event Schema",
"strictMode": false,
"fields": [
{
"path": "type",
"type": "string",
"required": true,
"constraints": {
"enum": ["payment_intent.succeeded", "payment_intent.payment_failed", "charge.succeeded"]
}
},
{
"path": "data.object.amount",
"type": "number",
"required": true,
"constraints": { "min": 0, "max": 99999999 }
},
{
"path": "data.object.currency",
"type": "string",
"required": true,
"constraints": { "minLength": 3, "maxLength": 3, "pattern": "^[a-z]{3}$" }
},
{
"path": "data.object.customer",
"type": "string",
"required": false
},
{
"path": "data.object.metadata",
"type": "object",
"required": false
}
],
"inputNodes": [{ "nodeType": "webhook", "nodeId": "6654a1b2c3d4e5f6a7b8c9d0" }],
"isActive": true,
"ownerId": "org_abc123",
"createdAt": "2025-05-01T12:00:00.000Z",
"updatedAt": "2025-05-01T12:00:00.000Z"
}Update a Schema Validator
curl -X PATCH /api/schema-validators/6655a7b8c9d0e1f2a3b4c5d6 \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"strictMode": true,
"fields": [
{
"path": "type",
"type": "string",
"required": true,
"constraints": {
"enum": ["payment_intent.succeeded"]
}
},
{
"path": "data.object.amount",
"type": "number",
"required": true,
"constraints": { "min": 1 }
},
{
"path": "data.object.currency",
"type": "string",
"required": true
}
]
}'strictMode can be disruptive — any fields in the payload not explicitly defined in the schema will cause validation to fail. Test thoroughly before enabling on production webhooks.Canvas Integration
Schema Validator nodes use the ID prefix sv- on the canvas and are rendered in cyan (#06b6d4). They connect to webhooks and run early in the onEventCreated phase — before the rest of the pipeline, and after the primary delivery to the webhook's target URL has already been queued.
{
"id": "sv-6655a7b8c9d0e1f2a3b4c5d6",
"type": "schema-validator",
"position": { "x": 250, "y": 200 },
"data": {
"label": "Payment Event Schema",
"schemaValidatorId": "6655a7b8c9d0e1f2a3b4c5d6",
"strictMode": false,
"fieldCount": 5,
"isActive": true
}
}Payload Examples
Valid payload — passes validation
{
"type": "payment_intent.succeeded",
"data": {
"object": {
"amount": 5000,
"currency": "usd",
"customer": "cus_abc123"
}
}
}
// Result: PASSES — all required fields present, types match, constraints satisfiedInvalid payload — blocked
{
"type": "invoice.finalized",
"data": {
"object": {
"amount": -500,
"currency": "US"
}
}
}
// Validation errors, in the wording the validator actually produces:
// - "type": must be one of: payment_intent.succeeded, charge.refunded
// - "data.object.amount": must be >= 0
// - "data.object.currency": must be at least 3 charactersWhat a rejection leaves behind
A rejection is the node doing its job, not a breakdown, so it is recorded as a warning rather than an error — an event stopped in its tracks is not just another line in the noise either. The log entry carries the errors in its metadata, and the node's lastInvalidPayload keeps the body that failed, next to the lastValidPayload of the last one that passed.
{
"level": "warn",
"source": "schema_validator",
"message": "Schema \"Payment Event Schema\" INVALID — 2 error(s)",
"statusCode": 422,
"nodeName": "Payment Event Schema",
"success": false,
"metadata": {
"valid": false,
"strictMode": false,
"fieldCount": 5,
"errors": [
{ "path": "data.object.amount", "message": "must be >= 0" },
{ "path": "data.object.currency", "message": "must be at least 3 characters" }
]
}
}Answering a Sync webhook
The Schema Validator answers a webhook in Sync response mode — the only other node that can is the AI Node, and Sync cannot be switched on for a flow that carries neither. It answers at its own step, before dispatching anything downstream, so the caller's connection is held for one validation rather than for the length of the workflow.
payload fails → this validator's error response (422 by default)
payload passes → held until the last validator on the path has passed,
then 200 { "accepted": true, "eventId": "…" }
and the rest of the flow keeps running behind itIn None response mode nothing changes: the validator blocks the pipeline exactly as described above and nobody is waiting on an answer.
Reply only mode
A Schema Validator has two modes. Validate — mode: "validate", the default — is everything described above. Reply only — mode: "reply" — validates nothing: when the run reaches the node, whoever is holding the connection open (the caller of a webhook in Sync response mode) gets the response you configured on it, and the connection closes. The payload goes through untouched and everything downstream is dispatched as usual.
It is meant for the end of an error branch. A Conditional or a Filter decides the event is one you will not process — an order that was already handled, a customer you do not know, an API version you no longer accept — and the branch it routes to ends in a Reply-only validator that tells the caller so, with a status and a body of your own. A validating Schema Validator can only say “the payload does not match the schema”; a Reply-only one says whatever you decide, on whichever branch you put it.
Webhook, Sync mode
└── Schema Validator (Validate)
│ fails → 422, pipeline stops
│ passes → noted, connection still open
└── Conditional payload.status == "processed"
├── true → Schema Validator (Reply only)
│ 409 { "error": "Order … was already processed" } — final,
│ and whatever hangs below it still runs
└── else → HTTP Action → …
nothing else answers: the Validate step's 200, as usualWhat you configure
Two fields, sent on POST /api/schema-validators and PATCH /api/schema-validators/:id, and edited in the validator's panel once the mode is switched. Both parts of reply are optional.
| Field | Range | Default | What it does |
|---|---|---|---|
| mode | validate, reply | validate | Which of the two the node is. Switching to reply does not touch fields, strictMode or syncResponse — they are kept, and ignored until you switch back. |
| reply.status | 200–599 | 200 | The HTTP status the caller gets. Any 2xx, 4xx or 5xx — this is your answer, not a verdict on the payload, so the 2xx-only and 4xx-only ranges of Validate mode do not apply. Out of range is refused with a 400 at save time; a value that reaches the response builder anyway comes out as 200, not clamped. |
| reply.body | JSON object | { "accepted": true, "eventId": "…" } | The body, sent as JSON. Every string value in it is a template: {{payload.orderId}} is replaced with the event's value, pipes and all, exactly as in the other nodes — see Expressions & pipes. Strings at any depth are rendered; numbers and booleans are left alone. |
In the panel you do not type that JSON: the body box takes lines. One key: value per line; indent under a bare key: to nest — key: value lines make an object, - value lines make a list, and - key: value starts an object inside a list. Numbers and true/false are typed; wrap a value in quotes to keep it as text. Pasting JSON works too. The same box is used for the custom input under Run Test → Advanced.
accepted: true
order:
id: {{payload.orderId}}
tags:
- checked
- {{payload.source}}
items:
- sku: A1
qty: 2{
"accepted": true,
"order": { "id": "ord_1042", "tags": ["checked", "stripe"] },
"items": [{ "sku": "A1", "qty": 2 }]
}What the caller receives is the status, the rendered body with content-type: application/json; charset=utf-8, and the X-HostWebhook-Event-Id header that every sync response carries — you do not need to put the event id in the body yourself.
Final, like a rejection — but nothing stops
A pass from a validating Schema Validator is provisional: the connection stays open in case a later validator on the path says no. A Reply-only validator is that later word. Its reply goes out the moment the run reaches it and the provisional pass is discarded — the caller never sees the 200 the earlier validator would have given. Unlike a rejection, though, it does not stop the pipeline: the payload is handed on and the nodes after it run behind the answer, just as they do behind a pass.
Example
Switch an existing validator to Reply only and give it a 409 with a message built from the payload:
curl -X PATCH /api/schema-validators/6655a7b8c9d0e1f2a3b4c5d6 \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"mode": "reply",
"reply": {
"status": 409,
"body": { "error": "Order {{payload.orderId}} was already processed" }
}
}'A caller whose event, { "orderId": "ord_8841", … }, is routed to that validator gets:
HTTP/1.1 409 Conflict
content-type: application/json; charset=utf-8
X-HostWebhook-Event-Id: 6f3a91c2b45d8e0197ac2d64
{
"error": "Order ord_8841 was already processed"
}Leave reply out, or send it as null, and the same validator answers 200 with { "accepted": true, "eventId": "…" } — the plain acknowledgement, given from wherever on the canvas you put the node.
Use Cases
- Contract enforcement — Ensure incoming webhooks always match a defined schema before any processing occurs.
- Data quality gates — Block events with missing or malformed fields from polluting downstream databases.
- API versioning — Validate that payloads match a specific API version's expected structure using
enumconstraints on version fields. - Strict mode for security — Enable
strictModeto reject payloads with unexpected fields, preventing injection of unwanted data. - Custom error answers — End an error branch with a validator in Reply only mode to answer a Sync caller with a status and body of your own — a
409for an order already processed, a410for a retired API version. - Debugging — Attach a schema validator to catch malformed events early and see detailed validation error reports in the dashboard.
API Reference
| Method | Webhook | Description |
|---|---|---|
| GET | /api/schema-validators | List all schema validators |
| POST | /api/schema-validators | Create a new schema validator |
| GET | /api/schema-validators/:id | Get a specific schema validator |
| PATCH | /api/schema-validators/:id | Update a schema validator |
| DELETE | /api/schema-validators/:id | Delete a schema validator |