How do I only forward webhooks that match a condition?
Evaluate a rule against each event after it is verified and parsed, forward it only to the destinations whose rule accepts it, and count the events no rule accepted instead of dropping them silently. Check every rule against a payload you actually received: a rule that reads a field the payload does not have matches nothing, raises no error, and looks exactly like a quiet day.
Forward by rule, count the rest
const TIMED_OUT = Symbol('timedOut')
// Add one to counts[name] as an own property, so a destination named
// "__proto__", "constructor" or "toString" is counted like any other.
function bump (counts, name) {
const value = (Object.hasOwn(counts, name) ? counts[name] : 0) + 1
Object.defineProperty(counts, name, { value, enumerable: true, writable: true, configurable: true })
}
// Forward an event only to the destinations whose rule accepts it, and count what nothing accepted.
// routes: [{ name, when(event) -> boolean, send(event) -> Promise }]
// pass the returned stats into the next call to keep one running count
export async function forwardMatching (event, routes, stats = { unmatched: 0, sent: {}, failed: {}, timedOut: {} }, timeoutMs = 10_000) {
const matching = routes.filter((route) => route.when(event))
if (matching.length === 0) stats.unmatched++
// Send to every matching destination at once, each with its own deadline,
// so one that fails or hangs never holds up the others or the next event.
await Promise.all(matching.map(async (route) => {
let timer
const deadline = new Promise((resolve) => { timer = setTimeout(resolve, timeoutMs, TIMED_OUT) })
try {
const result = await Promise.race([route.send(event), deadline])
bump(result === TIMED_OUT ? stats.timedOut : stats.sent, route.name)
} catch {
bump(stats.failed, route.name)
} finally {
clearTimeout(timer)
}
}))
return stats
}Why count the unmatched
A rule with a typo in a field path is still a valid rule; it just never matches. Nothing throws, nothing returns an error, and the destination sees an empty day. The unmatched count is the only signal that separates "no events came" from "the rule reads the wrong field".
Check the rule against a real payload
Take a payload you received and run the rule on it by hand before you rely on it. LinkedIn, for example, puts the event type at data.type, not at the top of the body where Stripe and Square put it — The LinkedIn recipe walks through the rule that matched nothing.
In Slashbin
Each destination has its own filter rules, evaluated against the Golden Model record — for example, production orders only with tags does not contain "staging" — so one event can go to some destinations and not others. See How do I send one webhook to multiple destinations?
Need somewhere for the next project's webhooks to land? Slashbin's Free plan takes an account, not a card.
Start Free
(opens in a new tab)