Write, test, and attach JavaScript (or Lua) transformation functions that run on Webhook Relay to modify webhooks in flight — reshape the JSON body, rename/ reformat fields, add or remove headers,...
A function is server-side code attached to an input or output. It runs on
every request that passes through and can rewrite the request before it is
forwarded, set a custom response, or stop forwarding entirely. The default and
recommended runtime is JavaScript (driver js, powered by goja). Lua
(lua) is also supported.
Where a function runs:
Your code runs against a global request object r. There is no function(){}
wrapper — just write top-level statements.
Read the incoming request (read-only):
r.body — raw request body stringr.method, r.path, r.rawQueryr.headers — map of request headersr.query — parsed query paramsr.formData — parsed form dataMutate the request (forwarded to the destination):
r.setBody(string)r.setHeader(name, value) / r.deleteHeader(name)r.setMethod(string) — e.g. "POST"r.setPath(string) / r.setRawQuery(string)Control the response returned to the original caller:
r.setResponseBody(string)r.setResponseStatus(code) — e.g. 200, 400r.setResponseHeader(name, value)Flow control:
r.stopForwarding() — do not forward this request (use for filtering/dropping)Other globals:
JSON.parse(...) / JSON.stringify(...) — standard, always available; use
these for JSON.cfg.get("KEY") — read a stored config value / secret so you never hard-code
tokens. Set values with relay function config set <function> KEY=VALUE (or in
the dashboard); list/remove with relay function config ls|rm <function>.console.log(...) / console.warn(...) / console.error(...) — appear in
function logs.Newer platform versions also expose helper modules (
httpfor outbound calls,cryptofor hashing/HMAC/base64,time, plusmailgun/bigquery). Prefer the standardJSON+r.*API above for portability; reach for the modules only when needed and confirm availability in the dashboard. See https://webhookrelay.com/docs/webhooks/functions for the current reference.
camelCase, setBody-style, and PascalCase (SetRequestBody) names are all
accepted; this skill uses the idiomatic camelCase aliases.
Use the Webhook Relay MCP server first when it is connected. It can create functions, execute them with synthetic requests, attach them to inputs/outputs, and inspect delivery logs after live traffic runs through them.
Use the relay CLI only when MCP is not connected but the CLI is authenticated
and working, or when you need a CLI-only capability such as local file-based test
specs.
Put the code in a .js file. See examples/ in this skill:
examples/to-slack.js — reshape a generic event into a Slack messageexamples/filter.js — drop requests that don't match a conditionexamples/add-auth-header.js — inject an auth header from configexamples/spec.yaml — a test spec for relay function testIf the Webhook Relay MCP server exposes runtime docs, read its JavaScript API
resource before writing or updating code. JavaScript transforms run in a custom
runtime and mutate the global r object directly; do not write Node.js/browser
code or handler wrappers.
When MCP is connected:
create_function (driver: js).execute.attach_function, or pass the function
ID while creating a bucket/output.list_function_execution_logs to find failed or
slow runs and get_function_execution_log for the full original/modified
request, error, response context, and persisted console output. Webhook logs
also expose input/output/response-function execution IDs that can be passed
directly to get_function_execution_log.For output formatting, prefer attaching the function to the output so other outputs in the same bucket can receive the original request if needed.
relay function test runs your code against sample requests and asserts the
result. Use this when MCP is unavailable or when a checked-in spec file is the
most convenient validation path. Set driver: js in the spec (the .js
extension also implies it).
relay function test -f examples/spec.yaml -v
Assertions available under expect.request / expect.response:
bodyModified, bodyEquals, bodyContains, headerModified, headerEquals,
methodEquals, pathEquals, pathContains, queryContains, statusEquals.
CLI version note.
relay function testandrelay function invokefor JavaScript need a recentrelayCLI. Older versions fail with a Luasyntax erroron.jsfiles (the driver wasn't honored) orunknown field "metadata" in reactor_v1.Requeston invoke. If you hit either, runrelay --versionand update the CLI (https://webhookrelay.com/docs/installation/cli). Deployed functions run server-side and are unaffected.
relay function create --name to-slack --driver js --source examples/to-slack.js
relay function ls
Update later with:
relay function update to-slack --source examples/to-slack.js
# attach when creating the output
relay output create -b to-slack --type public \
-d https://hooks.slack.com/services/T000/B000/XXXX \
--function to-slack
# or attach inline while forwarding
relay forward --type public -b to-slack -f to-slack \
https://hooks.slack.com/services/T000/B000/XXXX
# attach to an input instead (to shape the response to the provider)
relay input create -b my-app "incoming" --function to-slack
relay function invoke to-slack -m POST -b '{"message":"deploy finished"}' \
--header content-type=application/json
// Convert a generic JSON webhook into a Slack message.
const data = JSON.parse(r.body)
r.setBody(JSON.stringify({ text: "New event: " + (data.message || "n/a") }))
r.setHeader("Content-Type", "application/json")
JSON.stringify before r.setBody — the body is a string.JSON.parse in try/catch; set a 400
response and stopForwarding() on bad payloads).cfg.get(...), not in source.execute and
get_function_execution_log.execute before attaching to live traffic. If MCP is not
available, use relay function test before deploying.Webhook Relay function docs (these .md URLs render as plain markdown for easy reading):