Quickstart
This page takes you from nothing to a verified webhook. You need curl and a public HTTPS endpoint for the last part. No email or card is involved.
#Make a first call
No key is needed. Anonymous requests share a limit of 30 units per minute per network.
curl -s "https://farbeamlabs.com/api/v1/transfers?limit=1"const res = await fetch("https://farbeamlabs.com/api/v1/transfers?limit=1");
const { data, meta } = await res.json();
console.log(data[0], meta.next_cursor);import requests
body = requests.get("https://farbeamlabs.com/api/v1/transfers?limit=1", timeout=30).json()
print(body["data"][0], body["meta"]["next_cursor"])The response is one transfer row plus meta. This one was captured on 2026-11-04 UTC with the same call.
{
"data": [
{
"id": 2777,
"uid": "ethereum:0x89c96f0a0692825a110fb8042b9c4148602783d479c116510bf56edc326d542a:412",
"ts": "2026-11-04T19:29:23.000Z",
"chain": "ethereum",
"block": 26343546,
"tx": "0x89c96f0a0692825a110fb8042b9c4148602783d479c116510bf56edc326d542a",
"kind": "token",
"token": "USDC",
"token_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"amount": 114510.23,
"usd": 114473.12868548,
"from": "0x0000000000000000000000000000000000000000",
"to": "0xfb1898bb5955fdd11704e397104c6a0e0725eb17",
"sampled": false,
"from_label": "Null address",
"from_type": "infrastructure",
"to_label": null,
"to_type": null
}
],
"meta": {
"request_id": "req_c88814402090",
"as_of": "2026-11-04T19:29:42.544Z",
"next_cursor": 2777,
"min_stored_usd": 100000,
"retention_days": 7
}
}The data model page explains every field.
#Create a key
A free key raises the limit to 120 units per minute and is required for alert rules. Open the console, sign in with a Solana wallet, and create a key. Signing sends no transaction and costs nothing. The key is shown once. To do the same over the API, see authentication.
Save the value in FARBEAM_KEY, then check it.
export FARBEAM_KEY="paste-your-key-here"
curl -s "https://farbeamlabs.com/api/v1/me" -H "Authorization: Bearer $FARBEAM_KEY"#Write and test a rule
Alert rules use Beamline. Test the expression against stored transfers before you save it. This call needs no key.
curl -s -X POST "https://farbeamlabs.com/api/v1/rules/validate" \
-H "content-type: application/json" \
-d '{"expression":"usd >= 5m and token in [\"USDC\",\"USDT\"]"}'The response holds valid, tested, matched, up to 10 sample rows and the list of fields.
#Run a webhook receiver
Your endpoint must be public HTTPS on port 443. These receivers read the raw body, verify the signature, and answer 200 within the 6 second delivery timeout. Set FARBEAM_SECRET to the signing secret from the next step, and put TLS in front with your usual proxy.
// receiver.mjs: run with node receiver.mjs
import { createServer } from "node:http";
import { createHmac, timingSafeEqual } from "node:crypto";
const secret = process.env.FARBEAM_SECRET;
function verify(header, raw) {
const parts = Object.fromEntries((header || "").split(",").map((p) => p.split("=")));
if (!parts.t || !parts.v1) return false;
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const want = createHmac("sha256", secret).update(`${parts.t}.`).update(raw).digest();
const got = Buffer.from(parts.v1, "hex");
return got.length === want.length && timingSafeEqual(got, want);
}
createServer((req, res) => {
const chunks = [];
req.on("data", (c) => chunks.push(c));
req.on("end", () => {
const raw = Buffer.concat(chunks);
if (!verify(req.headers["x-farbeam-signature"], raw)) {
res.writeHead(401).end();
return;
}
const event = JSON.parse(raw);
console.log(event.type, event.data?.uid, event.data?.usd);
res.writeHead(200).end("ok");
});
}).listen(8000);# receiver.py: run with python3 receiver.py
import hashlib, hmac, json, os, time
from http.server import BaseHTTPRequestHandler, HTTPServer
SECRET = os.environ["FARBEAM_SECRET"].encode()
def verify(header, raw):
parts = dict(p.split("=", 1) for p in (header or "").split(",") if "=" in p)
if "t" not in parts or "v1" not in parts:
return False
if abs(time.time() - int(parts["t"])) > 300:
return False
want = hmac.new(SECRET, parts["t"].encode() + b"." + raw, hashlib.sha256).hexdigest()
return hmac.compare_digest(want, parts["v1"])
class Hook(BaseHTTPRequestHandler):
def do_POST(self):
raw = self.rfile.read(int(self.headers.get("content-length", 0)))
if not verify(self.headers.get("x-farbeam-signature"), raw):
self.send_response(401)
self.end_headers()
return
event = json.loads(raw)
print(event["type"], (event["data"] or {}).get("uid"), (event["data"] or {}).get("usd"))
self.send_response(200)
self.end_headers()
HTTPServer(("", 8000), Hook).serve_forever()#Save the rule
Replace the URL with your own endpoint. The response holds signing_secret once.
curl -s -X POST "https://farbeamlabs.com/api/v1/rules" \
-H "Authorization: Bearer $FARBEAM_KEY" \
-H "content-type: application/json" \
-d '{"name":"Large stablecoin moves","expression":"usd >= 5m and token in [\"USDC\",\"USDT\"]","url":"https://example.com/hooks/farbeam","format":"json"}'#Send a test delivery
Use the rule id from the last response. A test delivery has type rule.test and data: null.
curl -s -X POST "https://farbeamlabs.com/api/v1/rules/$RULE_ID/test" \
-H "Authorization: Bearer $FARBEAM_KEY"
sleep 5
curl -s "https://farbeamlabs.com/api/v1/rules/$RULE_ID/deliveries?limit=1" \
-H "Authorization: Bearer $FARBEAM_KEY"The log row should read "status":"delivered" with your HTTP code. If it reads pending with an error, see testing webhooks.
#Next steps
- Webhooks for the payload format and delivery rules.
- Beamline examples for tested rules you can copy.
- Pagination for reading more than one page.
- The console if you prefer a browser form for keys and rules.