Webhook icon
TransformValue icon
If icon
Fail icon
Request icon
SlackIncomingWebhook icon

Validate and Reshape a Webhook Order Payload with JSONata

Orchestrate JSON payload validation and reshaping with Kestra and JSONata.

Categories
Data

Inbound webhooks rarely arrive in the shape a downstream system wants, and forwarding a payload without checking it first is how a missing customer ID or an empty line-item array turns into a broken record somewhere else. This blueprint uses io.kestra.plugin.transform.jsonata.TransformValue to do both jobs in one pass: it checks the incoming order JSON for the fields the downstream order service actually requires, and reshapes the nested structure into a flat record with computed totals, all with a single JSONata expression instead of a custom script.

How it works

  1. order_webhook (io.kestra.plugin.core.trigger.Webhook) exposes a signed endpoint that receives the raw order JSON as {{ trigger.body }}.
  2. validate_and_reshape (io.kestra.plugin.transform.jsonata.TransformValue) runs a single JSONata expression against the payload. It builds a missing_fields list by checking order.id, order.customer.id, order.currency, and a non-empty order.line_items array, then returns valid (true only when nothing is missing), the missing_fields list, and a flattened order object with order_id, customer_id, customer_email, currency, item_count, a computed total_amount ($sum of qty * unit_price across every line item, rounded with $round), and a reshaped line_items array with a per-line line_total.
  3. reject_invalid_payload (io.kestra.plugin.core.flow.If) checks {{ outputs.validate_and_reshape.value | jq('.valid') | first }}. When it's false, the nested fail_on_missing_fields (io.kestra.plugin.core.execution.Fail) stops the flow with the specific missing fields in the error message, so forward_to_order_service never runs for a bad payload.
  4. forward_to_order_service (io.kestra.plugin.core.http.Request) only executes after validation passes, POSTing the flattened order object to the downstream order service.
  5. notify_success confirms the order ID, item count, and total in Slack; the errors block posts a separate alert if validation rejects the payload or the downstream call fails.

What you get

  • Field-level validation of a nested webhook payload without a dedicated schema-validation task, expressed entirely in one JSONata expression.
  • A flattened, downstream-ready record with computed totals, instead of the caller's original nested shape.
  • A hard stop before forwarding: a malformed payload never reaches the order service, and the failure message names exactly what was missing.
  • Slack visibility into both successful forwards (with order totals) and rejected or failed payloads.

Who it's for

  • Integration teams accepting webhooks from an e-commerce platform, marketplace, or partner system that don't control the payload shape.
  • Data engineers who need to reshape a nested API payload into a flat record before loading it into a database, queue, or downstream API.
  • Teams currently validating and reshaping JSON in a custom script who want the same logic expressed declaratively in the flow itself.

Why orchestrate this with Kestra

A webhook receiver with no validation step either crashes downstream on the first malformed payload, or you end up writing and maintaining a custom script just to check a few required fields and flatten some nested arrays. Kestra's TransformValue task runs that logic as a single, versioned, testable expression right in the flow, and the If/Fail combination makes rejection an explicit, alertable step instead of a silent downstream failure.

Prerequisites

  • An upstream system capable of POSTing order JSON to a Kestra webhook URL.
  • A downstream order-processing API that accepts a POST with the flattened order shape.
  • A Slack incoming webhook for notifications.

Secrets

  • ORDER_WEBHOOK_KEY: the key segment for this flow's webhook URL. Treat it as a secret; anyone with it can trigger the flow.
  • ORDER_SERVICE_URL: base URL of the downstream order-processing API.
  • SLACK_WEBHOOK_URL: Slack incoming webhook URL.

Quick start

  1. Add the three secrets above to your Kestra namespace.
  2. Point your upstream system at /api/v1/{tenant}/executions/webhook/{namespace}/jsonata-webhook-payload-reshape/{ORDER_WEBHOOK_KEY}.
  3. Send a test payload shaped like {"order": {"id": "...", "customer": {"id": "...", "email": "..."}, "currency": "USD", "line_items": [{"sku": "...", "qty": 1, "unit_price": 9.99}]}} and confirm it forwards successfully.
  4. Remove a required field from a test payload and confirm the flow fails with the specific missing field named in the error message.

How to extend

  • Add more required fields to the $missing array as the downstream service's contract grows, without touching any other task.
  • Swap forward_to_order_service for a database insert, a queue publish, or a call to another Kestra flow via Subflow.
  • Use io.kestra.plugin.transform.jsonata.TransformItems instead when the webhook delivers a batch of orders as a JSON array rather than one at a time.
  • Route reject_invalid_payload failures to a dead-letter queue or a review namespace so rejected payloads aren't just logged and dropped.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.