Webhooks Quickstart
Receive real-time notifications when on-chain events happen. No polling required. Bootnode will POST a signed JSON payload to your endpoint whenever a matching event occurs.
Supported Event Types
| Event Type | Description |
|---|---|
| address_activity | Any transaction involving specified addresses (sends, receives, contract interactions) |
| token_transfer | ERC-20 token transfers to/from specified addresses or contracts |
| nft_transfer | ERC-721 and ERC-1155 NFT transfers |
| mined_transaction | Specific transaction hash has been mined and confirmed |
| log | Contract event logs matching specific topics and addresses |
Create a Webhook Subscription
Register your endpoint and specify which events you want to receive. Bootnode returns a signing_secret that you will use to verify webhook payloads.
curl -X POST https://api.bootnode.dev/v1/webhooks \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{
"url": "https://myapp.com/api/webhooks/bootnode",
"chain": "ethereum",
"event_type": "address_activity",
"config": {
"addresses": [
"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"
]
}
}'Response:
{
"id": "wh_a1b2c3d4e5f6",
"url": "https://myapp.com/api/webhooks/bootnode",
"chain": "ethereum",
"event_type": "address_activity",
"config": {
"addresses": [
"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"
]
},
"signing_secret": "whsec_k7m9p2q4r6s8t0v2w4x6y8z0",
"status": "active",
"created_at": "2026-01-15T10:30:00Z"
}Important
Save the signing_secret securely. It is only returned once during creation. You will need it to verify incoming webhook signatures.
Set Up Your Webhook Endpoint
Create an Express.js server (or any HTTP server) to receive webhook events. Bootnode sends POST requests with a JSON body and an HMAC signature in the headers.
// server.ts
import express from "express";
import crypto from "crypto";
const app = express();
const SIGNING_SECRET = process.env.WEBHOOK_SIGNING_SECRET!;
// "whsec_k7m9p2q4r6s8t0v2w4x6y8z0"
// IMPORTANT: Use raw body for signature verification
app.post(
"/api/webhooks/bootnode",
express.raw({ type: "application/json" }),
(req, res) => {
const signature = req.headers["x-bootnode-signature"] as string;
const timestamp = req.headers["x-bootnode-timestamp"] as string;
const body = req.body as Buffer;
// 1. Verify the signature
if (!verifySignature(body, signature, timestamp)) {
console.error("Invalid webhook signature");
return res.status(401).json({ error: "Invalid signature" });
}
// 2. Parse the event
const event = JSON.parse(body.toString());
console.log("Received event:", event.event_type, event.data);
// 3. Handle the event
switch (event.event_type) {
case "address_activity":
handleAddressActivity(event.data);
break;
case "token_transfer":
handleTokenTransfer(event.data);
break;
case "nft_transfer":
handleNftTransfer(event.data);
break;
default:
console.log("Unhandled event type:", event.event_type);
}
// 4. Return 200 to acknowledge receipt
res.status(200).json({ received: true });
}
);
app.listen(3001, () => {
console.log("Webhook server listening on port 3001");
});Verify the HMAC Signature
Every webhook request includes two headers for verification: X-Bootnode-Signature and X-Bootnode-Timestamp. The signature is a HMAC-SHA256 hex digest of {timestamp}.{body}.
function verifySignature(
body: Buffer,
signature: string,
timestamp: string
): boolean {
// 1. Reject old timestamps (prevent replay attacks)
const now = Math.floor(Date.now() / 1000);
const ts = parseInt(timestamp, 10);
if (Math.abs(now - ts) > 300) {
// 5-minute tolerance
return false;
}
// 2. Compute expected signature
const payload = `${timestamp}.${body.toString()}`;
const expected = crypto
.createHmac("sha256", SIGNING_SECRET)
.update(payload)
.digest("hex");
// 3. Constant-time comparison
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}Handle Event Types
Each event type has a specific payload structure. Here are handlers for the most common event types:
interface AddressActivityEvent {
chain: string;
block_number: number;
tx_hash: string;
from: string;
to: string;
value: string; // wei as decimal string
asset: string; // "ETH" or token symbol
contract: string | null; // null for native transfers
direction: "incoming" | "outgoing";
}
function handleAddressActivity(data: AddressActivityEvent) {
console.log(
`[${data.chain}] ${data.direction} ${data.asset}: ` +
`${data.from} -> ${data.to} (${data.value} wei)`
);
// Example: notify user of incoming transfer
if (data.direction === "incoming") {
notifyUser(data.to, `Received ${data.asset} from ${data.from}`);
}
}
interface TokenTransferEvent {
chain: string;
block_number: number;
tx_hash: string;
contract: string;
from: string;
to: string;
value: string;
symbol: string;
decimals: number;
}
function handleTokenTransfer(data: TokenTransferEvent) {
const formatted = (
Number(BigInt(data.value)) / Math.pow(10, data.decimals)
).toFixed(data.decimals);
console.log(
`[${data.chain}] Token transfer: ${formatted} ${data.symbol}`
);
}
interface NftTransferEvent {
chain: string;
block_number: number;
tx_hash: string;
contract: string;
from: string;
to: string;
token_id: string;
standard: "ERC721" | "ERC1155";
quantity: number; // always 1 for ERC721
}
function handleNftTransfer(data: NftTransferEvent) {
console.log(
`[${data.chain}] NFT transfer: #${data.token_id} ` +
`(${data.standard}) from ${data.from} to ${data.to}`
);
}
function notifyUser(address: string, message: string) {
// Your notification logic (email, push, in-app, etc.)
console.log(`Notify ${address}: ${message}`);
}Example Webhook Payload
Here is a full example of what Bootnode sends to your endpoint:
// Headers:
// X-Bootnode-Signature: 5a2f8c9d1e3b4a6f7c8d9e0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b
// X-Bootnode-Timestamp: 1706400000
// Content-Type: application/json
// Body:
{
"id": "evt_z9y8x7w6v5u4",
"event_type": "address_activity",
"webhook_id": "wh_a1b2c3d4e5f6",
"created_at": "2026-01-15T10:35:22Z",
"data": {
"chain": "ethereum",
"block_number": 20574335,
"tx_hash": "0xabc123def456789...",
"from": "0x1234567890abcdef1234567890abcdef12345678",
"to": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"value": "1000000000000000000",
"asset": "ETH",
"contract": null,
"direction": "incoming"
}
}Retry Policy
If your endpoint returns a non-2xx status code or does not respond within 15 seconds, Bootnode retries the delivery with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 10 seconds |
| 2nd retry | 1 minute |
| 3rd retry | 10 minutes |
| 4th retry | 1 hour |
| 5th retry | 6 hours |
After 5 failed retries, the webhook is paused and you receive an email notification. You can resume it from the dashboard or via the PATCH endpoint.