How to Build a Validate-Payload Subworkflow for Webhook Inputs
Build a reusable child workflow in Spojit that inspects an incoming webhook payload, checks its required fields and formats, and returns a clean pass or fail result that any parent workflow can branch on.
What This Integration Does
External systems rarely send perfect data. A webhook payload might be missing a field, carry a malformed email address, or send an amount as text instead of a number. If every parent workflow has to repeat the same defensive checks, you end up copying validation logic into dozens of places and fixing the same bug many times over. This tutorial shows you how to centralize those checks in a single Subworkflow that takes a payload, runs it through the validation and json connectors, and hands back a structured verdict like { "valid": true, "errors": [] }. Any parent workflow then invokes it as one step and uses a Condition node to either continue processing or post a rejection to Slack.
The child workflow starts from a Manual trigger so it can be called by other workflows. The parent passes the raw webhook body in as the child's Input; the child reads that input, evaluates each rule, and assembles a result object as its final step. The child's final output returns to the parent, which receives the verdict as the Subworkflow node's output while it is paused. Each invocation is stateless: it leaves nothing behind and reads nothing from a prior run, so re-running a parent or replaying a webhook simply re-validates the same payload and produces the same verdict. Children appear as their own entries in execution history, so you can inspect exactly which rule failed for a given payload.
Prerequisites
- A Spojit workspace where you can create two workflows: the validation child and at least one parent.
- The validation and json connectors (built in, no authentication or connection required).
- A Slack connection if you want the parent to post rejections to a channel. Add it under Connections -> Add connection -> Slack, and note the channel name you will post to.
- A sample webhook payload to design against. The example below assumes an order-style body with
email,orderId, andamountfields.
Step 1: Create the validation child workflow
Create a new workflow named something clear like validate-order-payload. Add a Trigger node and set its type to Manual. The Manual trigger's output is the request body passed to it, which is exactly what a parent will supply through the Subworkflow node's Input. While designing, use the Run button with a sample body so the rest of the canvas has realistic data to bind to:
{
"email": "buyer@example.com",
"orderId": "ORD-10428",
"amount": "129.50"
}
Throughout the child you reference these fields as {{ trigger.email }}, {{ trigger.orderId }}, and {{ trigger.amount }}.
Step 2: Confirm required fields are present
Add a Connector node in Direct mode using the json connector with the get tool to read a specific field, or use the keys tool to list the top-level keys present in the payload. The simplest presence check is to run json validate on the stringified body first to confirm it is well-formed JSON, then read individual fields. Map the tool input to {{ trigger }} and store the result in an output variable such as {{ keys_result }}. You will compare against this in Step 4 to detect a missing email, orderId, or amount. Keep this node in Direct mode: presence checks are deterministic and should not consume AI credits.
Step 3: Validate each field's format
Add one or more Connector nodes in Direct mode on the validation connector, one tool per rule:
- validation
emailwith the value input set to{{ trigger.email }}, output variable{{ email_check }}. - validation
numericwith the value input set to{{ trigger.amount }}, output variable{{ amount_check }}. This confirms the amount is a parseable number even when it arrives as a string. - Optionally validation
alphanumericon{{ trigger.orderId }}if your order IDs follow a fixed character set, output variable{{ orderid_check }}.
Each validation tool returns whether the value passed its rule. Run these in a Parallel node if you want all format checks to evaluate concurrently rather than one after another.
Step 4: Assemble a single verdict object
Add a Transform node to combine the individual check results into one verdict your parents can rely on. Build an object with a boolean valid and an errors array, then collapse the array into the boolean. For example, produce intermediate fields and use the json connector set tool to construct the final shape:
{
"valid": false,
"errors": [
"email is not a valid address",
"amount is not numeric"
],
"orderId": "{{ trigger.orderId }}"
}
Reference each upstream result, for example {{ email_check }} and {{ amount_check }}, when deciding which messages to add. Keeping orderId on the verdict gives the parent something to log or display without re-reading the original payload. Store the finished object in an output variable such as {{ verdict }}.
Step 5: Make the verdict the child's final output
The child's final node output is what returns to the parent's Subworkflow node, so make sure the node that builds {{ verdict }} (the Transform node, or the json connector node from Step 4) is the last step on the canvas with nothing after it. Save the child workflow. Because the verdict shape is now fixed, every parent can branch on the same two fields (valid and errors) regardless of which payload was validated.
Step 6: Call the child from a parent webhook workflow
Open or create the parent workflow that receives real traffic. Add a Trigger node set to Webhook and attach a signing connection (schemes include Spojit, Shopify, GitHub, Slack, or Custom) so payloads are verified before they run. The trigger's output is the parsed JSON body. Next, add a Subworkflow node, pick your validate-order-payload child under Workflow, and set its Input to the webhook body, for example {{ trigger }}. The parent pauses while the child runs and then continues with the verdict available as the Subworkflow node's output, for example {{ validation_result.valid }} and {{ validation_result.errors }}.
Step 7: Branch on the result
Add a Condition node that checks whether {{ validation_result.valid }} is true. Route the true branch into the rest of your processing (creating an order, writing to a database, and so on). Route the false branch into a Connector node in Direct mode on the Slack connector using the send-message tool, with the channel set and the text built from the rejection details:
Payload rejected for order {{ validation_result.orderId }}:
{{ validation_result.errors }}
This gives your team an immediate, readable signal whenever bad data arrives, while clean payloads flow straight through.
Tips
- If you want the child to make a judgment call that is hard to express as discrete rules (for example "does this address look like a real shipping address"), switch that one check to a Connector node in Agent mode with a Response Schema so it still returns structured JSON. Keep the rest in Direct mode to avoid unnecessary AI credit usage.
- Reuse the same child across every parent that ingests the same payload shape. A fix or a new rule added to the child takes effect immediately for all parents, since changes to a child apply to all callers.
- Use the json connector
picktool in the child to strip the payload down to only the fields you validate, which keeps the verdict object small and the execution history easy to read. - You can ask Miraxa, the intelligent layer across your automation, to scaffold this for you with a prompt like "Add a Subworkflow node that calls validate-order-payload, pass the webhook body as input, then a Condition node on the valid field," and fine-tune the fields afterward in the properties panel.
Common Pitfalls
- The child uses a Manual trigger, not a Webhook trigger. The webhook lives on the parent; the child only needs to accept whatever the parent passes as
Input. Giving the child its own Webhook trigger means it never receives the parent's payload. - Webhook payloads can be replayed. Because validation is stateless it will simply re-run, but make sure the downstream processing on the true branch is safe to repeat, or enable the trigger's opt-in dedup using an event-id header.
- Numbers often arrive as strings. Validate
{{ trigger.amount }}with the validationnumerictool rather than assuming a number type, and convert it explicitly in a Transform node before any math. - Keep the verdict shape stable. If you rename
validor changeerrorsfrom an array to a string, every parent's Condition node must be updated. Treat the child's final output shape as a contract. - Avoid deep nesting. Calling a validation child from another child from another child becomes hard to trace; keep validation one level below the parent that owns the webhook.
Testing
Validate the child on its own first: open validate-order-payload and use the Run button with a deliberately broken body (a bad email, a non-numeric amount, a missing field) and confirm the final output returns valid: false with the expected messages, then run it again with a clean body to confirm valid: true and an empty errors array. Once the child is reliable, test the parent end to end by sending a small webhook with a known-bad payload and checking that the false branch posts to your Slack channel, then send a clean payload and confirm the true branch runs. Inspect both the parent and child entries in execution history to see exactly which rule fired for each test.