Farbeam
Docs / Alerts and webhooks

Webhooks

When a rule matches, Farbeam sends an HTTP POST with a JSON body to the rule URL. This page defines the request, what your endpoint must return, and the limits.

#The request

HTTP
POST /hooks/farbeam HTTP/1.1
Host: example.com
content-type: application/json
content-length: <body length>
user-agent: farbeam-webhooks/1
x-farbeam-signature: t=1793821030,v1=<64 hex characters>
x-farbeam-rule: rule_c3208c320585
HeaderValue
content-typeapplication/json
user-agentfarbeam-webhooks/1
x-farbeam-signaturet=<unix seconds>,v1=<hex>. See webhook signatures.
x-farbeam-ruleThe rule id

The URL path and query are sent as you saved them. A query string token in your URL is a way to add routing data, though the signature is what proves the sender.

#Payload

There are three types: transfer.matched, liquidity.matched and rule.test.

FieldTypeMeaning
typestringtransfer.matched, liquidity.matched or rule.test
rule_idstringRule id
rule_namestringRule name when the delivery was queued
sent_atstringUTC time the delivery was queued. It stays the same across retries.
dataobject or nullThe transfer or the liquidity sample, or null on rule.test

#transfer.matched

The data object uses the fields of the transfer object. The row in this sample is real, stored on 2026-11-04 UTC. The envelope was built with the service's own formatter, with an example rule.

JSON
{
  "type": "transfer.matched",
  "rule_id": "rule_c3208c320585",
  "rule_name": "Large moves",
  "sent_at": "2026-11-04T19:37:10.000Z",
  "data": {
    "uid": "ethereum:0xcc9606e4d3febb135994e9f4035de34c791f2c0cd58bee75c8a7df40e488841a:210",
    "ts": 1793821007000,
    "chain": "ethereum",
    "block": 26343583,
    "tx": "0xcc9606e4d3febb135994e9f4035de34c791f2c0cd58bee75c8a7df40e488841a",
    "kind": "token",
    "token": "WETH",
    "token_address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
    "amount": 743.5137099679833,
    "usd": 1800098.7377921855,
    "from": "0x76f30e3f75437fb862b8d2c4d80a671bceba5b1a",
    "to": "0x000000000035b5e5ad9019092c665357240f594e",
    "sampled": false,
    "id": 3485,
    "from_label": null,
    "from_type": null,
    "to_label": null,
    "to_type": null
  }
}

#liquidity.matched

A rule that names liquidity_usd or change_pct sends this type. See Beamline fields. This sample is an example built from the service's event shape. It is not a real reading.

JSON
{
  "type": "liquidity.matched",
  "rule_id": "rule_000000000000",
  "rule_name": "Liquidity drop",
  "sent_at": "2026-11-04T12:00:03.000Z",
  "data": {
    "uid": "liq:solana:JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN:1793880000000",
    "ts": 1793880000000,
    "kind": "liquidity",
    "chain": "solana",
    "token": "JUP",
    "token_address": "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN",
    "liquidity_usd": 1000000,
    "change_pct": -31.5,
    "window_minutes": 55
  }
}

#rule.test

JSON
{
  "type": "rule.test",
  "rule_id": "rule_c3208c320585",
  "rule_name": "Large stablecoin moves",
  "sent_at": "2026-11-04T19:37:09.496Z",
  "data": null
}

Handle data: null in your code, or a test delivery will crash your receiver.

#What your endpoint must do

RequirementDetail
Answer with 2xxAny code from 200 to 299 counts as delivered. The body is ignored.
Answer within 6 secondsLonger is a timeout failure
Verify the signatureOn the raw bytes, before you parse the JSON
Remove duplicatesUse data.uid. See below.
Do slow work laterPut the event on your own queue, then answer

A redirect (3xx) counts as a failure, because redirects are not followed. Use the final URL.

#Duplicates

A delivery can arrive more than once. Your endpoint may finish its work and fail to answer in time, and the retry then repeats it. There is no delivery id in the payload, so use data.uid plus rule_id as your key.

#Ordering

Deliveries are not ordered. A retry can arrive after newer events. Use data.block or data.ts if order matters.

#URL rules

These rules run when you create or change a rule, with the error code invalid_url.

RuleMessage
Must parse as a URLurl is not a valid URL
Must use HTTPSurl must use https
No user name or password in the URLurl must not contain credentials
Port must be empty or 443url must use port 443
At most 500 charactersurl is too long
Must be a host name. No IP addresses. No localhost, *.local or *.internal.url must use a public host name

#SSRF protection

The service resolves your host name again before each send. All returned addresses must be public, and the connection uses the address that was checked, so a change in DNS between check and connect cannot redirect it. If any address is private, the attempt fails with host resolves to a private address.

These ranges are refused.

FamilyRanges
IPv4private ranges (RFC 1918), loopback, link-local, carrier-grade NAT, benchmark ranges, multicast and reserved ranges
IPv6::, ::1, fc00::/7, fe80::/10, ff00::/8, and IPv4-mapped forms of the IPv4 ranges

Host names are checked at create time for form only. DNS is checked at send time, so a rule can save and then fail its first delivery with this error.

#Limits

LimitValueWhen it is reached
Cooldown60 to 86,400 s, default 300After a rule fires, matches are dropped until it ends. No log row is written, and they are not sent later.
Deliveries per rule per hour120Further matches in that UTC hour are dropped. No log row is written, and they are not sent later.
Rules per account10POST /rules returns 409 rule_limit. Disabled rules count.
Test deliveries per rule10 per hour429 rate_limited
Failures in a row before auto-off20 deliveriesThe rule turns off. See below.
Send timeout6 seconds per attemptCounts as a failed attempt
Log retention14 daysRows are deleted

#The cooldown and the hourly cap

After a rule fires, it waits for cooldown_seconds before it can fire again. Read last_fired_at on the rule to see the last time. Set cooldown_seconds when you create or update a rule. Because the shortest cooldown is 60 s, matches alone reach at most 60 per hour, and the cooldown acts before the cap.

The count resets at the start of each UTC hour. Test deliveries add to the count, though the cap does not block them. A retry does not add to it. If a rule reaches 120, you lose the rest of the matches for that hour with no notice in the log. If you need a count of what you missed, compare against List large transfers with the same filters. If you hit the cap often, raise the usd floor.

#Auto-off

A delivery has failed when all four attempts have failed. Twenty such deliveries in a row turn the rule off, and enabled reads false with consecutive_failures at 20. One success resets the count to 0.

No notice is sent when this happens. Monitor it with List rules. To turn the rule back on, fix your endpoint, then send PATCH /rules/{id} with {"enabled":true}. This also clears the count. Matches during the off period are not sent later.

#Delivery log

List deliveries returns recent rows for a rule.

JSON
{
  "id": 1,
  "ts": "2026-11-04T19:22:56.834Z",
  "transfer_uid": "test",
  "status": "pending",
  "attempts": 1,
  "code": null,
  "error": "connection failed"
}
FieldValues
statuspending, delivered or failed
attemptsAttempts so far, up to 4
codeYour last HTTP status, or null
errornon-2xx reply, timeout, connection failed, a URL safety message, or null
transfer_uidThe row uid, or test

The payload itself is cleared once a delivery ends, either way. See privacy and data.

#More