DocumentationFundamentals

Alerting from functions

Inspect webhook requests and delivery responses in Webhook Relay functions, and POST an alert to any URL when something looks wrong.

Functions can do more than reshape payloads — they can inspect the request or the delivery response and alert an external system when something is off.

Two common patterns:

  1. Request function (runs before delivery) — parse the body; if it is not valid JSON (or a required field is missing), POST an alert to a URL.
  2. Response function (runs after delivery) — if the destination returned an empty body, POST an alert to a URL.

Set alert_webhook_url as a config variable on the function so the alert endpoint is not hard-coded in the source.

For a deeper look at post-delivery hooks, status codes, and rewriting the recorded response, see Response (post-delivery) functions.

Example 1: alert when the request is not valid JSON

Attach this as a request function (before delivery). It tries to parse r.body as JSON. If parsing fails, it posts an alert to your URL and stops forwarding so a bad payload never reaches the destination.

// Request function — runs BEFORE delivery.
// Alert if the body is missing or is not valid JSON.

var url = cfg.get("alert_webhook_url");

if (!r.body || r.body.trim() === "") {
  if (url) {
    http.post(url, JSON.stringify({
      type: "invalid_request",
      reason: "empty body",
      output: r.metadata["output_name"],
      path: r.path
    }), { headers: { "Content-Type": "application/json" } });
  }
  r.stopForwarding();
  return;
}

var payload;
try {
  payload = JSON.parse(r.body);
} catch (e) {
  if (url) {
    http.post(url, JSON.stringify({
      type: "invalid_request",
      reason: "body is not valid JSON",
      error: String(e),
      output: r.metadata["output_name"],
      path: r.path
    }), { headers: { "Content-Type": "application/json" } });
  }
  r.stopForwarding();
  return;
}

// Optional: also require a field
if (!payload || payload.id == null) {
  if (url) {
    http.post(url, JSON.stringify({
      type: "invalid_request",
      reason: "missing required field: id",
      output: r.metadata["output_name"]
    }), { headers: { "Content-Type": "application/json" } });
  }
  r.stopForwarding();
  return;
}

// Body looks fine — continue delivery as-is (or call r.setBody(...) to reshape it).
-- Request function — runs BEFORE delivery.
-- Alert if the body is missing or is not valid JSON.

local http = require("http")
local json = require("json")

local url = cfg:GetValue("alert_webhook_url")

local function alert(reason, extra)
  if url == "" then return end
  local body = {
    type = "invalid_request",
    reason = reason,
    output = r.metadata["output_name"] or "",
    path = r.RequestPath or ""
  }
  if extra then
    for k, v in pairs(extra) do body[k] = v end
  end
  local payload, err = json.encode(body)
  if err then error(err) end
  http.request("POST", url, {
    headers = { ["Content-Type"] = "application/json" },
    body = payload
  })
end

if r.RequestBody == nil or r.RequestBody == "" then
  alert("empty body")
  r:StopForwarding()
  return
end

local payload, err = json.decode(r.RequestBody)
if err then
  alert("body is not valid JSON", { error = tostring(err) })
  r:StopForwarding()
  return
end

if payload == nil or payload.id == nil then
  alert("missing required field: id")
  r:StopForwarding()
  return
end

-- Body looks fine — continue delivery as-is
-- (or call r:SetRequestBody(...) to reshape it).

Example 2: alert when the response body is empty

Attach this as a response function (after delivery). When the destination answers with a success status but an empty body — a common silent-failure pattern — post an alert to your URL.

// Response function — runs AFTER delivery.
// Alert if the destination returned an empty body.

var empty = !r.responseBody || r.responseBody.trim() === "";
if (!empty) {
  return; // body present — nothing to do
}

var url = cfg.get("alert_webhook_url");
if (!url) {
  return;
}

http.post(url, JSON.stringify({
  type: "empty_response",
  reason: "destination returned an empty body",
  status: r.responseStatus,
  output: r.metadata["output_name"],
  destination: r.metadata["output_url"]
}), { headers: { "Content-Type": "application/json" } });
-- Response function — runs AFTER delivery.
-- Alert if the destination returned an empty body.

local http = require("http")
local json = require("json")

local empty = r.ResponseBody == nil or r.ResponseBody == ""
if not empty then
  return -- body present — nothing to do
end

local url = cfg:GetValue("alert_webhook_url")
if url == "" then
  return
end

local payload, err = json.encode({
  type = "empty_response",
  reason = "destination returned an empty body",
  status = r.ResponseStatus,
  output = r.metadata["output_name"] or "",
  destination = r.metadata["output_url"] or ""
})
if err then error(err) end

http.request("POST", url, {
  headers = { ["Content-Type"] = "application/json" },
  body = payload
})

You can combine this with a status check (r.responseStatus === 0 or >= 400) if you also want alerts on timeouts and HTTP errors. See Response (post-delivery) functions for failure-status examples and for rewriting the recorded response when a 200 should count as failed.

Setup checklist

  1. Create a function in the dashboard (use the REQUEST tab for example 1, RESPONSE tab for example 2).
  2. Add a config variable alert_webhook_url pointing at your Slack incoming webhook, PagerDuty, Discord, or any HTTP endpoint that accepts POST JSON.
  3. Attach the function to an output:
    • Request function → function_id (before delivery)
    • Response function → response_function_id (after delivery)
Did this page help you?