Contents

Webhooks

Signed HTTPS POSTs of every radar event to your endpoint within seconds of detection: whale moves, big liquidations, cascades, funding extremes and flips, OI spikes, whales near liquidation, records, exploits, depegs, TVL drops and CEX outflows.

POSThttps://your-server.example/hooks/blockmetricAPI Dev · 2 endpointsAPI Pro · 10 endpoints

Set up in four steps

  1. Create an endpoint on Account › Event webhooks. Create endpoints on /account/webhooks: URL, event kinds (none = all), minimum severity and an optional coin list (with a coin filter, events without a coin are not sent). The signing secret (whsec_ + 43 characters) is shown once; rotation keeps the old secret signing for 24 hours. Up to 2 endpoints per account with API Dev, 10 with API Pro. "Send test event" delivers a type "test" event immediately.
  2. Verify every delivery with the signing secret (code below). Reject anything that does not verify.
  3. Answer 2xx within 10 seconds, then do the work. Deliveries are at-least-once: deduplicate on Blockmetric-Event-Id.
  4. Send a test event from the account page: a test event signed like a real one arrives at once.

Verify the signature

Blockmetric-Signature: t=<unix seconds>,v1=<64 hex chars>[,v1=<64 hex chars>], an HMAC-SHA256 of "<t>.<raw body>" with your signing secret.

Verification
import crypto from 'node:crypto'

// rawBody = the exact bytes received (Buffer), header = the Blockmetric-Signature header
export function verify(rawBody, header, secret, now = Date.now()) {
  let t = NaN
  const sigs = []
  for (const part of header.split(',')) {
    const [k, v] = part.trim().split('=')
    if (k === 't') t = Number(v)
    if (k === 'v1') sigs.push(v)
  }
  if (!Number.isInteger(t) || Math.abs(now / 1000 - t) > 300) return false
  const expected = crypto.createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest()
  return sigs.some((s) => {
    const got = Buffer.from(s, 'hex')
    return got.length === expected.length && crypto.timingSafeEqual(got, expected)
  })
}
  • Compute the HMAC over the raw body bytes as received. Do not parse and re-serialise the JSON first.
  • Accept the request when any v1 value matches (constant-time comparison); ignore unknown keys (future versions such as v2).
  • Reject timestamps more than 300 seconds away from your clock (replay protection); keep your server clock in sync (NTP).
  • Retries are signed again with a fresh t; the body is byte-identical.

Receivers per framework

The signature covers the exact bytes. Each framework has its own way to get the raw body. Here it is.

Complete receiver
import crypto from 'node:crypto'
import express from 'express'

const app = express()
const seen = new Set() // use your database in production

function verify(rawBody, header, secret, nowSec = Math.floor(Date.now() / 1000)) {
  let t = NaN
  const sigs = []
  for (const part of String(header ?? '').split(',')) {
    const [k, v] = part.trim().split('=')
    if (k === 't') t = Number(v)
    if (k === 'v1' && v) sigs.push(v)
  }
  if (!Number.isInteger(t) || Math.abs(nowSec - t) > 300) return false
  const expected = crypto.createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest()
  return sigs.some((s) => {
    const got = Buffer.from(s, 'hex')
    return got.length === expected.length && crypto.timingSafeEqual(got, expected)
  })
}

// express.raw keeps the exact bytes: do not put express.json() in front of this route
app.post('/hooks/blockmetric', express.raw({ type: 'application/json' }), (req, res) => {
  if (!verify(req.body, req.get('Blockmetric-Signature'), process.env.BLOCKMETRIC_WEBHOOK_SECRET)) return res.status(400).send('invalid signature')
  const event = JSON.parse(req.body.toString('utf8'))
  res.status(204).end() // answer within 10 s, work afterwards
  if (seen.has(event.id)) return // at-least-once delivery: deduplicate on the id
  seen.add(event.id)
  console.log(event.type, event.data.severity, event.data.title)
})

app.listen(3000)

Request

POST with Content-Type application/json. Body: the event envelope, at most 64 KiB.

Webhook request headers
HeaderMeaning
Blockmetric-Signaturet=<unix seconds>,v1=<hex HMAC-SHA256>; during a secret rotation a second v1 signed with the previous secret follows.
Blockmetric-Event-IdSame as the body id ("evt_<id>"); identical on every retry.
Blockmetric-Event-TypeSame as the body type.
Blockmetric-Delivery-Attempt1 for the first attempt, up to 6.
User-AgentBlockmetric-Webhooks/1.0 (+<site>)
Event envelope fields
FieldDescription
idstring"evt_<radar event id>": stable across retries and identical to Blockmetric-Event-Id; use it to deduplicate. Test events: "evt_test_<hex>".
typestringThe event kind (one of EVENT_TYPES) or "test".
createdintegerUnix seconds of the event (data.ts).
createdAtstringThe same moment as ISO 8601 (API 1.14.0). Every other time in the API is ISO.
dataobjectExactly one item of GET /api/v1/radar/events: id, ts, kind, severity (1–4), coin, title, body, data, source. data.truncated: true when an oversized event was shortened (fetch it via /radar/events).
A real event
{
  "id": "evt_5468",
  "type": "funding_extreme",
  "created": 1793106660,
  "createdAt": "2026-10-27T13:11:00.000Z",
  "data": {
    "id": 5468,
    "ts": "2026-10-27T13:11:00.282Z",
    "kind": "funding_extreme",
    "severity": 1,
    "coin": "xyz:PURRDAT",
    "title": "xyz:PURRDAT funding at +128% APR on Hyperliquid",
    "body": "Hourly rate +0.0146% (longs pay shorts) · open interest $11.6M · mark $9.2451.",
    "data": {
      "venue": "hyperliquid",
      "fundingApr": 1.28301588,
      "fundingHourly": 0.000146463,
      "openInterestUsd": 11556929.71,
      "mark": 9.2451
    },
    "source": "hyperliquid"
  }
}

type is one of whale_open, whale_close, whale_liquidated, whale_near_liq, big_liquidation, liq_cascade, funding_extreme, funding_flip, oi_spike, record, smart_money, hack, depeg, tvl_drop, cex_outflow, hl_market_change, hl_manipulation, hl_adl, oracle_deviation, vault_drawdown, liq_extreme, protocol_outflow, incident, big_transfer, stablecoin_mint, stablecoin_burn, dex_whale_trade, risk_wallet_move, risk_wallet_listed or test. Filter by kind, severity and coin per endpoint.

Delivery and retries

Success
Any 2xx status within 10 seconds. The response body is ignored (the first 2 KiB are shown in the delivery log).
Retries
Attempt 1 immediately, then 1 min, 5 min, 30 min, 2 h and 12 h after the previous failure (6 attempts over ~15 h). Timeouts, network errors, 3xx, 4xx (except 410) and 5xx count as failures.
Disabling
HTTP 410 Gone disables the endpoint immediately. 50 consecutive failed attempts disable the endpoint. Disabling cancels queued deliveries; enabling again resets the failure counter and delivers only new events.
Your URL
https:// on port 443, host names that resolve to public addresses only (no private, loopback, link-local or reserved ranges; checked on every connection). Redirects are not followed.
Order
Usually in event order per endpoint, but not guaranteed (retries), so order by data.ts / data.id. At-least-once delivery: deduplicate on the envelope id.
Volume
Up to 300 delivery attempts per account and minute; the rest follows in the next cycles.
Failed deliveries and fixes
Delivery log showsFix
Timeout (no answer within 10 s)Answer 2xx first, then process the event in the background or a queue.
3xx redirectRedirects are not followed. Register the final https:// URL.
400 from your serverUsually a failed signature check: verify the raw bytes (no JSON parser in front), the right secret, and a synced clock.
401 / 403 from your serverYour auth middleware blocks the route. Exclude the webhook path; the signature is the authentication.
410 GoneDisables the endpoint on purpose. Enable it again on the account page.
5xx or network errorRetried up to 6 times over about 15 hours; 50 failures in a row disable the endpoint.
TLS errorA valid certificate for the host name on port 443. Self-signed certificates and private addresses are refused.

Prefer one open connection to many deliveries? The live stream sends the same envelope over Server-Sent Events.

Code snippets in these docs are licensed under MIT-0: copy them into your own code without attribution. The SDKs, CLI and MCP server will be, once published, Apache-2.0; the name Blockmetric is not licensed (forks must be renamed). Data from the API is governed by the API terms; third-party exchange data by the exchange’s terms.

LiveHyperliquid markets 7sOperationalHyperliquid positions & whales 6sOperationalLiquidation heatmap & levels 9sOperationalLighter markets & liquidations 15sOperationalLighter positions & heatmap 7sOperationalLighter RH positions & heatmap 11sOperationalBinance markets 36sOperationalBybit markets 6sOperationalBinance liquidations 15sOperationalBybit liquidations 15sOperationaldYdX positions & heatmap 8sOperationalGMX positions & heatmap 11sOperationalLong/short & open interest history 2mOperationalOrder books & trade flow 6sOperationalOn-chain transfers 7sOperationalSecurity monitor 45sOperationalBlockmetric Sentiment 9mOperationalOfficial news 8mOperationalAlert delivery 7sOperational10:06:33 UTCData: Blockmetric Research · Binance · Bybit