Transform webhook payloads and create dynamic URLs to match your system's exact requirements. Eliminate middleware with JSONata expressions.
Field Nation webhooks support customizable payloads and dynamic URLs, giving you complete control over how webhook data is structured and delivered.
How it works: events with no custom script deliver the standard payload, while events with a custom script are transformed and have any URL templates resolved before delivery.
Transform Payloads
Restructure webhook data to match your system's expected format using JSONata expressions.
Dynamic URLs
Include event data in webhook URLs for intelligent routing (e.g., route by region or company).
Filter Fields
Send only the fields you need — reduce payload size and processing overhead.
Rename Fields
Match your system's naming conventions without building middleware.
Without customization, integrating with external systems requires:
With payload customization, transformations happen at the source:
| Before | After |
|---|---|
| Build middleware to transform | Transform at delivery |
| Maintain translation servers | No extra infrastructure |
| One fixed payload format | Unlimited custom formats |
| Static webhook URLs | Dynamic, data-driven URLs |
Challenge: ServiceNow expects incident tickets in a specific format with custom field names.
JSONata Script:
{
"u_external_ticket_id": $string($.workorder.id),
"short_description": $.workorder.title,
"state": $.workorder.status.name = "Work Done" ? "6" : "2",
"assignment_group": "Field Services",
"u_technician": $.workorder.assignee.user.first_name & " " & $.workorder.assignee.user.last_name,
"u_client_company": $.workorder.company.name,
"work_notes": "Status updated via Field Nation at " & $.timestamp
}Result: Direct webhook delivery to ServiceNow without middleware.
Challenge: Route work orders to region-specific endpoints automatically.
Dynamic URL Template:
https://api.company.com/{{$.workorder.location.country}}/workordersResult:
api.company.com/US/workordersapi.company.com/UK/workordersapi.company.com/CA/workordersChallenge: Send formatted notifications to Slack when work orders complete.
JSONata Script:
{
"text": "Work Order #" & $string($.workorder.id) & " - " & $.workorder.status.name,
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*" & $.workorder.title & "*\nStatus: " & $.workorder.status.name & "\nTechnician: " & $.workorder.assignee.user.first_name & " " & $.workorder.assignee.user.last_name
}
}
]
}Result: Native Slack notifications without a middleware service.
Challenge: A 15-year-old ERP only accepts flat JSON with uppercase field names.
JSONata Script:
{
"WORK_ORDER_ID": $.workorder.id,
"WORK_ORDER_TITLE": $.workorder.title,
"COMPANY_NAME": $.workorder.company.name,
"TECHNICIAN_NAME": $.workorder.assignee.user.first_name & " " & $.workorder.assignee.user.last_name,
"STATUS": $.workorder.status.name
}Result: Direct integration with legacy systems that can't be modified.
Challenge: Data team needs webhook events in a format optimized for their data warehouse.
JSONata Script:
{
"event_type": $.event.name,
"event_timestamp": $.timestamp,
"workorder_id": $.workorder.id,
"company_id": $.workorder.company.id,
"status": $.workorder.status.name,
"location_state": $.workorder.location.state
}Result: Webhook data flows directly into analytics pipeline without ETL processing.
JSONata is a powerful query and transformation language for JSON data.
Why JSONata? It's purpose-built for JSON transformation, has a concise syntax, and supports complex operations like filtering, sorting, and aggregation.
{
"order_id": $.workorder.id,
"title": $.workorder.title,
"status": $.workorder.status.name
}{
"technician_name": $.workorder.assignee.user.first_name & " " & $.workorder.assignee.user.last_name,
"location": $.workorder.location.city & ", " & $.workorder.location.state
}{
"order_id": $.workorder.id,
"is_complete": $.workorder.status.name = "Work Done",
"priority": $.workorder.status.code = "at_risk" ? "high" : "normal"
}{
"work_order_id": $.workorder.id,
"po_number": $.workorder.custom_fields.results.results[name="PO Number"].value,
"tags": $.workorder.tags.results.label,
"has_priority_tag": "Priority" in $.workorder.tags.results.label
}{
"work_order_id": $string($.workorder.id),
"title_uppercase": $uppercase($.workorder.title),
"timestamp": $now(),
"scheduled_date": $substringBefore($.workorder.schedule.service_window.start.utc, " ")
}service_window UTC values use a space-separated format (2026-02-04 16:00:00). Split on the space to extract the date portion.
For simpler use cases, merge static fields into every webhook payload:
{
"source": "field-nation",
"environment": "production",
"api_version": "3.0",
"integration_id": "your-integration-id"
}This adds the static fields to the original payload without transforming it.
Include values from the webhook payload in your endpoint URL using template expressions.
https://api.example.com/workorders/{{$.workorder.id}}/updatesTemplates use {{ expression }} syntax where expression is a JSONata path.
Restriction: Templates are not allowed in the domain/origin part of the URL for security reasons. https://{{$.malicious}}.example.com is not permitted.
All webhook events include these fields that you can use in transformations:
| Path | Type | Description |
|---|---|---|
$.event.name | string | Event name (e.g., workorder.status.work_done) |
$.event.params | object | Event-specific parameters |
$.timestamp | string | ISO 8601 timestamp when event occurred |
$.triggered_by_user.id | number | User ID who triggered the event |
| Path | Type | Description |
|---|---|---|
$.workorder.id | number | Unique work order ID |
$.workorder.title | string | Work order title |
$.workorder.description.html | string | Description (HTML) |
$.workorder.description.markdown | string | Description (Markdown) |
$.workorder.status.id | number | Status ID |
$.workorder.status.name | string | Status name |
$.workorder.status.code | string | Granular sub-status code (e.g. at_risk, on_my_way, checked_in) — more specific than the lifecycle status.name |
$.workorder.type_of_work.name | string | Type of work |
$.workorder.href | string | Deep link to the work order in Field Nation |
| Path | Type | Description |
|---|---|---|
$.workorder.company.id | number | Buyer company ID |
$.workorder.company.name | string | Buyer company name |
$.workorder.company.address.city | string | Buyer company city |
$.workorder.manager.first_name | string | Manager first name |
$.workorder.manager.last_name | string | Manager last name |
$.workorder.manager.id | number | Manager user ID |
$.workorder.manager.email | string | Manager email |
$.workorder.assignee.user.id | number | Assigned provider ID |
$.workorder.assignee.user.first_name | string | Provider first name |
$.workorder.assignee.user.last_name | string | Provider last name |
| Path | Type | Description |
|---|---|---|
$.workorder.location.address1 | string | Street address |
$.workorder.location.city | string | City |
$.workorder.location.state | string | State/Province |
$.workorder.location.zip | string | ZIP/Postal code |
$.workorder.location.country | string | Country code |
$.workorder.location.coordinates.latitude | number | Latitude |
$.workorder.location.coordinates.longitude | number | Longitude |
| Path | Type | Description |
|---|---|---|
$.workorder.schedule.service_window.start.utc | string | Scheduled start (UTC, format YYYY-MM-DD HH:mm:ss) |
$.workorder.schedule.service_window.end.utc | string | Scheduled end (UTC, format YYYY-MM-DD HH:mm:ss) |
$.workorder.schedule.service_window.mode | string | Schedule mode (e.g. exact) |
$.workorder.schedule.time_zone.name | string | IANA timezone (e.g. America/Chicago) |
$.workorder.pay.type | string | Pay type (fixed, hourly, etc.) |
$.workorder.pay.base.amount | number | Base pay amount |
$.workorder.pay.base.units | number | Base pay units (hours/devices) |
$.workorder.pay.total | number | Total pay |
| Path | Type | Description |
|---|---|---|
$.workorder.custom_fields.results | array | Custom-field category groups |
$.workorder.custom_fields.results[n].name | string | Category name (e.g. "General") |
$.workorder.custom_fields.results[n].results | array | Fields within the category |
$.workorder.custom_fields.results[n].results[m].name | string | Field name |
$.workorder.custom_fields.results[n].results[m].value | string | Field value |
$.workorder.tags.results | array | Tag objects |
$.workorder.tags.results[n].label | string | Tag label |
Before customizing payloads, make sure you have:
Navigate to Webhooks Dashboard and create a new webhook or edit an existing one.
Check the "Override default payload" option, then click "Edit Payload" to open the script editor.
In the editor, choose your transformation approach, then enter your script:
{
"ticket_id": $.workorder.id,
"title": $.workorder.title,
"status": $.workorder.status.name,
"technician": $.workorder.assignee.user.first_name & " " & $.workorder.assignee.user.last_name
}If needed, update your webhook URL to include template expressions:
https://api.example.com/workorders/{{$.workorder.id}}Save the webhook configuration and trigger a test event to verify the transformation works correctly.
Begin with basic field selection, then add complexity as needed:
// Start here
{ "id": $.workorder.id, "status": $.workorder.status.name }
// Then expand
{
"id": $.workorder.id,
"status": $.workorder.status.name,
"company": $.workorder.company.name,
"technician": $.workorder.assignee.user.first_name & " " & $.workorder.assignee.user.last_name
}Use conditional logic to provide defaults when a field may be absent:
{
"assignee_name": $.workorder.assignee.user.first_name ?
($.workorder.assignee.user.first_name & " " & $.workorder.assignee.user.last_name) :
"Unassigned"
}Use the webhook test feature to validate transformations before enabling in production.
Original payloads are automatically preserved in Field Nation for debugging and re-delivery. You can access them through the delivery logs.
Debugging Tip: If a transformation fails, the original payload is still available in the delivery log details. Use this to diagnose and fix your script.
Payload customization is configured through the webhookScript object on the webhook resource. Set it when creating a webhook, or add it to an existing one — most teams add it to a webhook that's already running.
Replace YOUR_ACCESS_TOKEN with an OAuth access token — see Prerequisites.
The transform only runs when enabled is true and a script is present. enabled defaults to false, so if you omit it the script is saved but never applied and the standard payload is delivered — the most common source of "my transform isn't working" confusion.
curl -X POST https://api-sandbox.fndev.net/api/v1/webhooks \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"events": ["workorder.status.work_done"],
"url": "https://api.example.com/workorders/{{$.workorder.id}}",
"method": "post",
"status": "active",
"webhookScript": {
"scriptLanguage": "jsonata",
"script": "{ \"id\": $.workorder.id, \"status\": $.workorder.status.name }",
"enabled": true
}
}'curl -X PUT https://api-sandbox.fndev.net/api/v1/webhooks/wh_9f8c3d21 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"webhookScript": {
"scriptLanguage": "jsonata",
"script": "{ \"id\": $.workorder.id, \"status\": $.workorder.status.name }",
"enabled": true
}
}'Get the webhookId (wh_9f8c3d21 above) from the response of the create call, or from List Webhooks.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
script | string | Yes (on create) | — | The transformation. A JSONata expression, or — when scriptLanguage is json — a JSON document supplied as a string. |
scriptLanguage | string | No | jsonata | "jsonata" or "json". |
enabled | boolean | No | false | Must be true for the transform to run; otherwise the standard payload is sent. |
script is always a string; escape inner quotes in JSON request bodies. url may contain {{ … }} templates, but not in the domain or origin.
Additional optional fields (
secret,notificationEmail,isIntegrationOnly,webhookAttribute) follow the standard schema — see the Webhooks API Reference.
Payload customization eliminates the need for middleware by transforming data at the source:
| Capability | Benefit |
|---|---|
| JSONata transformations | Restructure data to match any system |
| Dynamic URLs | Route webhooks based on event content |
| Enable/disable toggle | Test without deleting configurations |
| Original payload storage | Debug with full context |
Bottom line: Your webhooks, your format. No middleware required.
Payload Structure
Explore the full event payload so you know exactly which fields you can transform.
Handling Events
Process transformed payloads reliably with idempotency and async patterns.
Testing Webhooks
Validate your transformation locally before enabling it in production.
Monitoring Webhooks
Watch delivery logs to confirm your custom payloads arrive as expected.
Last updated on