Skip to content

Webhooks

Get an HTTP POST when something happens on-chain — no polling. You register an endpoint (your URL + an auto-generated signing secret), then attach subscriptions (chain + event type, optional filter).

Event catalog

event_typeFires
new_blockevery new block on the subscribed chain
address_activityactivity involving a watched address
token_transfertoken transfer events
contract_eventsmart-contract events
healthnode health phase changes (Healthy/Degraded)

Manage endpoints & subscriptions

bash
# 1. create an endpoint (the secret is shown ONCE — store it)
curl -X POST https://api.lab.au.ro/api/v1/projects/$PROJECT_ID/webhooks \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"url":"https://your-app.example.com/hooks/chain"}'
json
{
  "id": "8e516d88-...",
  "url": "https://your-app.example.com/hooks/chain",
  "secret": "whsec_<64 hex chars — shown only once>",
  "active": true
}
bash
# 2. subscribe it to events
curl -X POST https://api.lab.au.ro/api/v1/webhooks/$ENDPOINT_ID/subscriptions \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"chain":"eth","event_type":"new_block"}'

# list / delete
curl https://api.lab.au.ro/api/v1/projects/$PROJECT_ID/webhooks -H "Authorization: Bearer $TOKEN"
curl -X DELETE https://api.lab.au.ro/api/v1/webhooks/$ENDPOINT_ID -H "Authorization: Bearer $TOKEN"
curl -X DELETE https://api.lab.au.ro/api/v1/subscriptions/$SUB_ID -H "Authorization: Bearer $TOKEN"

Endpoint URLs must be publicly reachable: private/internal addresses (10/8, 172.16/12, 192.168/16, 169.254/16, localhost) are rejected at delivery time as SSRF protection.

Delivery format

POST /hooks/chain HTTP/1.1
Content-Type: application/json
X-Signature: sha256=2aa607f07ae440554d7357cc202f26b493f780882d9160844e0eb24815e16967
json
{
  "chain": "eth",
  "event_type": "new_block",
  "data": {
    "chain": "eth",
    "event_type": "new_block",
    "block_num": 25302222,
    "block_hash": "0x...",
    "timestamp": 1781277200,
    "data": {}
  }
}

Respond with any 2xx within 10 seconds to acknowledge.

Verify the signature

X-Signature is sha256= + HMAC-SHA256 of the raw request body with your endpoint's whsec_* secret. Always compare in constant time:

js
import crypto from 'node:crypto'

export function verify(rawBody, signatureHeader, secret) {
  const calc = 'sha256=' +
    crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
  return crypto.timingSafeEqual(Buffer.from(calc), Buffer.from(signatureHeader ?? ''))
}

// express: app.post('/hooks/chain', express.raw({type:'*/*'}), (req,res) => {
//   if (!verify(req.body, req.get('X-Signature'), process.env.WEBHOOK_SECRET))
//     return res.status(401).end()
//   ... handle JSON.parse(req.body) ...
//   res.status(200).end()
// })
python
import hmac, hashlib

def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
    calc = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(calc, signature_header or "")

# flask: request.get_data() is the raw body; verify BEFORE json parsing

Both implementations above were validated against a live delivery — the signature in the example header is the real HMAC of the example body under a (revoked) test secret.

Retries & dead-lettering

Failed deliveries (non-2xx, timeout, connection error) are retried with backoff: 1 s → 5 s → 30 s → 5 min → 30 min (5 attempts total). Retry state is durable — dispatcher restarts don't lose pending deliveries. After the last failure the delivery is parked as failed with the last error and status code retained for inspection.

Design your handler to be idempotent (use block_hash/event identity): a slow 200 can race a retry.

Filters

A subscription may carry a JSON filter; an event matches when every filter key equals the corresponding event-data key:

json
{"chain":"eth","event_type":"address_activity","filter":{"address":"0xabc..."}}