How to Transform a Webhook Payload Before Forwarding
To transform a webhook payload before forwarding, set a CanHook relay rule's transform_mode to template, which rebuilds the body from {{dot.path}} tokens, or filter, which keeps only the keys you name. Both run before delivery, so the receiver gets exactly the shape it expects.
A webhook payload rarely arrives in the shape your downstream service wants. Stripe wraps the fields you actually need inside data.object; GitHub nests commit details three levels deep; Shopify sends more line-item metadata than most integrations will ever read. CanHook is a webhook inspector and relay: it catches an inbound webhook at a dedicated URL, stores the raw request, and can forward it to a destination you control. When forwarding, a relay rule can also transform the body first — rewriting it from a template or filtering it down to a named set of keys — so the receiver gets exactly the payload it expects, with no custom parsing on your side.
What Is Webhook Payload Transformation?
Webhook payload transformation is the process of rewriting or filtering an inbound request body before it is forwarded to a second service. It exists because the format a sender uses to describe an event rarely matches the format a receiver wants to consume, and reshaping the payload at the relay layer removes that mismatch from every integration you build afterward.
CanHook exposes this as a per-relay-rule transform_mode: passthrough forwards the raw inbound body unchanged, template rebuilds the body from a JSON template with {{dot.path}} placeholders, and filter keeps only a named subset of top-level keys. All three run before the request reaches your destination URL, so nothing downstream sees the untransformed original unless you choose passthrough. See the full set of capture and relay features for how transform rules fit alongside mock responses and retries.
Why Passthrough Relay Isn't Always Enough
Passthrough is the right default when a destination already speaks the sender's format — an internal service that parses Stripe events the way Stripe sends them, for instance. It stops being enough the moment you have more than one destination for the same webhook, or a destination that should never see certain fields at all: an analytics tool that needs three fields and not the customer's full billing address, a Slack channel that wants a one-line summary and not a raw JSON blob.
Hand-rolling that reshaping means parsing the sender's real payload, writing a transform function, and keeping it correct every time the sender adds or renames a field — separately, for every downstream service that needs a different shape. A CanHook relay rule does the same reshaping once, at the relay layer, so each receiver only ever sees the shape you've already tested against a real payload.
Transforming the body doesn't relax where it can be sent. Every relay destination is resolved and checked against private and reserved IP ranges before CanHook connects, including the 169.254.0.0/16 link-local range that cloud metadata endpoints use, and that check re-runs on every redirect hop. A transform changes the request body; it never changes which hosts a relay rule is allowed to reach.
Template Mode vs. Filter Mode: What's the Difference?
Template mode builds an entirely new body from a JSON structure you define; filter mode keeps the inbound body's original shape but drops every key you don't list. Pick template when the receiver expects a specific field layout of its own — an internal API, a chat webhook, a queue consumer. Pick filter when the receiver is fine with the sender's original shape but you want to shrink it, usually to strip sensitive fields or cut payload size before it reaches a shared destination.
| Mode | What it does | Good for |
|---|---|---|
| passthrough | Forwards the raw body unchanged | Destinations that already speak the sender's format |
| template | Rebuilds the body from a JSON template with {{dot.path}} tokens | Reshaping the payload into a format the receiver expects |
| filter | Keeps only the top-level keys you name, drops the rest | Trimming or removing sensitive fields before forwarding |
A template that pulls three fields out of a Stripe event object, which wraps the fields you usually want inside data.object, looks like this:
{
"template": {
"event_type": "{{type}}",
"amount_cents": "{{data.object.amount}}",
"customer": "{{data.object.customer}}"
}
}
A token that is the entire string, like {{data.object.amount}}, resolves to the source value's native type — a number stays a JSON number in the rebuilt body. A token mixed into other text always resolves as a string, and a path that doesn't exist in the payload resolves to an empty string instead of raising an error.
The equivalent filter rule, keeping only three top-level keys and dropping everything else in the inbound body:
{
"keys": ["id", "type", "created"]
}
How to Set Up a Transform Relay Rule
- Create an endpoint and copy its catch URL, then send it a real or realistic payload so you have an actual captured body to design against — see how to test and debug webhooks for capturing and inspecting that first request.
- Add a relay rule on the endpoint, set the destination URL, and choose
templateorfilteras the transform mode. - For template mode, write the JSON template using
{{dot.path}}tokens against the captured body's actual field names. For filter mode, list the keys to keep. - Save the rule, then replay the captured request to test the transform without waiting for the sender to fire another real event.
- Open the delivery log entry for that replay and read the exact transformed body CanHook sent, plus the destination's response status and duration.
- Adjust the template or key list and replay again until the destination accepts the payload the way you expect.
What Happens When a Relay Delivery Fails?
A failed relay delivery does not affect the response already sent to the webhook's original sender. CanHook returns the endpoint's configured mock response first, then runs relay delivery afterward, so a slow or failing destination never delays or changes the response the sender sees.
If the destination request fails or returns a non-2xx status, CanHook retries with exponential backoff — 60 seconds after the first failure, then 5 minutes, then 25 minutes, then up to a 2-hour gap between later attempts — up to the relay rule's configured retry count, which defaults to 3 attempts and can be raised to as many as 10. Every attempt, successful or not, is recorded in a per-destination delivery log with the response status, a snippet of the body, and how long the request took, so you can see exactly what a transform produced and how the receiver responded without adding logging to your own service.
Fanning a Single Webhook Out to Multiple Services
An endpoint can carry more than one relay rule, and each rule has its own transform mode and its own destination. A payment event can go out three ways at once: passthrough to the internal service that already expects the sender's raw shape, filter to an analytics endpoint that only needs a handful of fields, and template to a notification channel that wants a short, reshaped summary. Each rule retries and logs independently, so a failing analytics destination doesn't hold up the internal service or the notification.
The obvious objection is that you could write this fan-out and transform logic yourself, in the service that first receives the webhook, and for a single destination that's often true — it's a few lines of parsing code. It stops being true once you have three or four downstream services each wanting a different shape, or once a destination goes down and you need retries with backoff instead of a dropped request. At that point you're building a small relay system inside your webhook handler — a queue, a retry schedule, a delivery log — and maintaining that system is the real ongoing cost, not the transform logic itself.
Create a free endpoint, add a relay rule with transform_mode set to filter, and forward a real payload to a service you control — the free plan includes two endpoints, enough to test one rule without touching production. Create a free account and the first relay rule takes about two minutes to set up.
Frequently asked questions
What is the difference between template mode and filter mode in a CanHook relay rule?
Template mode rebuilds the outbound body from a JSON structure you define, using {{dot.path}} tokens to pull values out of the inbound payload. Filter mode keeps the inbound body's original shape but forwards only the top-level keys you list, dropping everything else before the request leaves CanHook.
Can a dot-path token reach a nested field like data.object.amount?
Yes. A {{dot.path}} token walks the parsed inbound JSON one segment at a time, so {{data.object.amount}} resolves to that nested value. A path that does not exist in the payload resolves to an empty string instead of causing an error, so a missing field will not break the relay.
Does a failed relay delivery delay the response sent to the webhook sender?
No. CanHook sends its configured mock response to the sender first, then runs relay delivery afterward, so a slow or failing destination never delays or changes the response the original sender receives, even if every relay attempt eventually fails.
How many times does CanHook retry a failed relay delivery?
Three attempts by default, backing off 60 seconds and then 5 minutes between tries. You can raise a rule's retry count up to 10 attempts total; later attempts back off further, capping at roughly a 2-hour gap between the last few tries.
Can one endpoint forward the same webhook to more than one destination?
Yes. An endpoint can carry multiple relay rules, each with its own destination URL, transform mode, and retry settings. One inbound webhook can go out as a raw passthrough to one service and a filtered or template-rebuilt body to another, at the same time.
Does filtering a webhook payload change where CanHook is allowed to send it?
No. Transform mode only changes the request body. Every destination is still resolved and checked against private and reserved IP ranges before CanHook connects, and that check re-runs on every redirect the destination sends back.