DocumentationFilter or drop webhooks before forwarding

Filter or drop webhooks before forwarding

Forward only the webhooks you want: drop events by type, branch, status or header, keep the rest out of your destination, and combine code filters with rule-based forwarding filters.

To filter webhooks, attach a function that inspects each request and calls r.stopForwarding() for anything the destination should not receive. Dropped requests still appear in the bucket's logs, the provider still gets a success response, and only matching events are delivered. For simple field, header or path conditions, forwarding rules do the same without code.

Keep only certain event types

const event = JSON.parse(r.body)

const allowed = ["invoice.paid", "invoice.payment_failed", "customer.subscription.deleted"]
if (!allowed.includes(event.type)) {
  r.stopForwarding()
  return
}
local json = require("json")

local event, err = json.decode(r.RequestBody)
if err then error(err) end

local allowed = { ["invoice.paid"] = true, ["invoice.payment_failed"] = true }
if not allowed[event.type] then
  r:StopForwarding()
  return
end

Only pushes to the main branch (GitHub)

const eventType = r.headers["X-Github-Event"] || r.headers["x-github-event"]
const payload = JSON.parse(r.body)

if (eventType !== "push" || payload.ref !== "refs/heads/main") {
  r.stopForwarding()
  return
}

Use the same shape for pull request action values (opened, closed), Shopify X-Shopify-Topic, or Jira issue events.

Drop requests that fail signature verification

Verify the HMAC before anything else and stop forwarding on mismatch, so forged requests never reach the destination. Store the secret as a secret service connection; the crypto functions page has the HMAC helpers and the signature guide shows each provider's header format.

const secret = require("secret:stripe-signing")

const signature = r.headers["Stripe-Signature"] || ""
const timestamp = (signature.match(/t=(\d+)/) || [])[1]
const v1 = (signature.match(/v1=([a-f0-9]+)/) || [])[1]
const expected = crypto.hmac("sha256", secret, `${timestamp}.${r.body}`)

if (!v1 || expected !== v1) {
  r.stopForwarding()
  return
}

Deduplicate retries

Providers retry on timeouts, so the same event can arrive twice. If your destination is not idempotent, key on the event id and skip repeats. Keep a short-lived record on the destination side, or rely on the provider's id and a durable retry schedule that avoids double delivery; see webhook retries and idempotency.

Rules instead of code

For conditions like "header equals", "JSON path equals", "path starts with" or "source IP in range", set a rule on the output and skip the function entirely. Rules and functions can be combined: a rule does the coarse filter, a function does the payload work. Details: forwarding rules and the filtering and routing guide.

Next: transform the payload of the events you keep, or alert when something unexpected arrives.

Frequently asked questions

In a function, inspect the parsed body and call r.stopForwarding() for anything you do not want; the request is logged but not delivered. For simple conditions on a field, header, path or query you can use forwarding rules on the output instead, with no code.

A normal success response from Webhook Relay, so it does not retry. The request is stored in the bucket's logs with no delivery, which keeps an audit trail of everything the provider sent.

Yes. Parse the payload, check that ref equals refs/heads/main (and the X-GitHub-Event header equals push), and stop forwarding otherwise. The same pattern works for pull request actions, Stripe event types or Shopify topics.

Yes. Put one output per destination on the bucket and give each output its own filter (rule or function), so pushes go to CI, releases go to Slack, and everything else is ignored.

Did this page help you?