Transforming payloads per destination
Six declarative ops that reshape a delivery before signing — and why we never run your JavaScript.
In short
- A transformation is an ordered list of steps on the endpoint; each step is one op.
- Six ops: pick, omit, rename, set, unwrap, wrap. Paths are dot-separated.
- Applied before signing, so the signature always covers the bytes that arrive.
- A failing transformation dead-letters — it never delivers the untransformed body.
What it is for
Three cases cover almost every real use. Stripping a field one destination must not see. Flattening our envelope for a receiver that wants its own shape. Tagging deliveries with a static field so the consumer can route them. All three are structural — none of them needs code.
The six ops
| Op | What it does |
|---|---|
{"omit": ["data.customer.email"]} | Removes paths. A missing path is a no-op, not an error |
{"pick": ["type", "data.order.id"]} | Keeps only the named paths — the body is rebuilt from them |
{"rename": {"data": "payload"}} | Moves a value to a new path |
{"set": {"source": "hookget"}} | Writes literal values (string, number, boolean, null) at paths |
{"unwrap": "data"} | The value at the path becomes the whole body |
{"wrap": "event"} | The whole body nests under this key |
Setting one
On creation or with a PATCH; null clears it and deliveries go back to the body
exactly as published:
curl -X PATCH https://api.hookget.com/v1/endpoints/ep_… -H "authorization: Bearer $HOOKGET_KEY" -d '{"transformation":[{"omit":["data.card"]},{"set":{"via":"hookget"}}]}'
Reshaping for a fixed-format receiver
A receiver that wants its own body, not our envelope — compose unwrap, pick and set:
[
{"unwrap": "data"},
{"pick": ["order.id", "order.total"]},
{"set": {"source": "hookget"}}
]
Delivers {"order":{"id":…,"total":…},"source":"hookget"} — signed over exactly
those bytes, so verification on the receiving side works unchanged.
The rules that keep it safe
- It is never code. Svix and Hookdeck run customer JavaScript; we chose not to. A delivery worker that executes customer code is a worker whose blast radius is every other customer's traffic. Six structural ops cover the real cases with nothing to escape from.
- Deterministic failures do not retry. The one runtime error is
unwrapof a missing path. The same input fails the same way every time, so the delivery dead-letters immediately withtransformation_failedinstead of burning the schedule. - Never untransformed. If a transformation fails, nothing is delivered. An
omitmay exist precisely to strip a field that must not reach that destination — failing open would be a data leak.
If you genuinely need logic — conditionals, arithmetic, lookups — do it in your publisher before the event enters the pipeline, where it is your code on your terms. The comparison names who runs JavaScript per destination if that is a requirement.