Transform a webhook payload
Reshape a webhook's JSON body before forwarding it: rename and map fields, flatten nested objects, convert one provider's event into the format another API or chat tool expects, using a JavaScript function.
To transform a webhook payload, attach a function to the output: parse the incoming body, build the object the destination expects, and set it as the new body. The function runs on Webhook Relay before every delivery attempt, so there is nothing to host and the provider keeps sending its normal webhook.
Rename and map fields
A provider sends { "event": "order.paid", "order": { "id": "o_1", "total_cents": 4990 } }; the destination wants { "type": "...", "orderId": "...", "amount": 49.90 }.
const incoming = JSON.parse(r.body)
const outgoing = {
type: incoming.event,
orderId: incoming.order.id,
amount: incoming.order.total_cents / 100,
receivedAt: new Date().toISOString(),
}
r.setBody(JSON.stringify(outgoing))
r.setHeader("Content-Type", "application/json")
Flatten nested objects and pick fields with JMESPath
For deeply nested provider payloads, the jmespath helper library extracts values with a query instead of chained property access:
const jmespath = require("jmespath")
const event = JSON.parse(r.body)
r.setBody(JSON.stringify({
customer: jmespath.search(event, "data.object.customer_details.email"),
items: jmespath.search(event, "data.object.lines.data[].{sku: price.product, qty: quantity}"),
}))
r.setHeader("Content-Type", "application/json")
See helper libraries for JMESPath, CSV, XML, YAML and templating modules.
Convert to a chat message (Slack, Discord, Teams)
Chat tools accept only their own message format. Build it from the event:
const event = JSON.parse(r.body)
r.setBody(JSON.stringify({
text: `New ${event.event}: order ${event.order.id} for ${event.order.total_cents / 100}`,
}))
r.setHeader("Content-Type", "application/json")
r.setMethod("POST")
Worked examples: Stripe webhooks to Slack, webhook to Discord, webhook to Microsoft Teams. The free message formatter generates this function from a sample payload.
Convert form data to JSON
HTML forms and some providers send application/x-www-form-urlencoded or multipart bodies. Parse them and emit JSON; see URL-encoded data and multipart form data. A worked example: Jotform to JSON.
Wrap or unwrap an envelope
Some destinations want the original event inside a wrapper, others want it unwrapped:
const event = JSON.parse(r.body)
// wrap: destination expects { source, payload }
r.setBody(JSON.stringify({ source: "stripe", payload: event }))
// unwrap: forward only the inner object
// r.setBody(JSON.stringify(event.data.object))
Test the transformation
- Capture a real payload with the free Webhook Bin or read one from the bucket's logs.
- Paste it into the function editor's test panel and run the function; the editor shows the resulting request.
- Send a test event from the provider and check the delivery log for the destination's response.
Next: modify headers, method and path, filter or drop webhooks, call an external API to enrich the payload. More copy-paste patterns in the transformation cookbook.
Frequently asked questions
Attach a function to the output. Parse r.body with JSON.parse, build the new object, and call r.setBody(JSON.stringify(newObject)). Set Content-Type with r.setHeader if the destination needs it. Webhook Relay forwards the new body; the provider never knows.
Yes. That is the main use: read the fields you need from the incoming event, write them into the field names the destination expects, and drop the rest. Helper libraries provide JMESPath for deep lookups and Mustache templates for text bodies.
Before delivery, on every attempt. If the destination fails, the retry sends the transformed request again. The original incoming request stays in the logs unchanged, so you can inspect what the provider actually sent.
Yes. Attach a different function to each output on the bucket. The same incoming event can become a Slack message on one output, a normalised JSON document on another, and pass through untouched on a third.
