Farbeam
Docs / Get started

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.

JSON
{
  "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.

curl
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
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
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
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