Introduction
The Chain Monitor API lets you manage wallet monitors and receive real-time transaction notifications across Ethereum, BSC, Polygon, Avalanche, and Fantom. Create an API key in Dashboard → API and use it for server-to-server requests.
Base URL
https://api.chain-monitor.online- • REST JSON API with predictable resource URLs
- • Ethereum, BSC, Polygon, Avalanche, Fantom
- • Real-time webhook notifications
Authentication
Authenticate with a Bearer token. Create an API key in Dashboard → API and pass it in the Authorization header.
Request Header
Authorization: Bearer api_key_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxKeep your API key secure. Do not share it publicly or commit it to version control. If compromised, rotate it in the dashboard.
Error Handling
The API uses standard HTTP status codes.
| Code | Description |
|---|---|
| 200 | Success |
| 201 | Resource created |
| 400 | Bad request — invalid parameters |
| 401 | Unauthorized — invalid or missing token |
| 403 | Forbidden — account suspended |
| 404 | Resource not found |
| 422 | Validation error |
| 429 | Rate limit exceeded |
| 500 | Internal server error |
Public (no auth)
These endpoints do not require auth. Use them to discover network UUIDs and token UUIDs before creating wallet monitors.
GET /public/networks
GET /public/networks — active networks and tokens (no auth).
Example response (JSON)
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Ethereum",
"slug": "ethereum",
"tokens": [
{ "id": "…", "name": "ETH", "slug": "eth", "type": "native" },
{ "id": "…", "name": "USDT", "slug": "usdt", "type": "token" }
]
}
]GET /public/pack-credits
GET /public/pack-credits — available credit packs (no auth).
Example response (JSON)
[
{
"id": "…",
"popular": false,
"count": "1000",
"price": 9.99,
"discount": 0,
"status": "active",
"createdAt": "…",
"updatedAt": "…"
}
]Wallets
Wallet monitors are blockchain addresses you track. Each monitor has a network, tokens, minimum amount, and optional webhook URL.
Get networkUuid and tokenUuids from GET /public/networks (no auth). Use network.id and token.id from the response.
GET /v1/wallets
Returns all wallet monitors for your account.
Example response (JSON)
[
{
"id": "…",
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1",
"networkUuid": "…",
"tokenUuids": ["…"],
"minAmount": "0",
"webhookUrl": null,
"status": "active",
"notifySettings": { "telegram": false, "webhook": true, "notifyIncoming": true, "notifyOutgoing": true },
"network": { /* … */ },
"tokens": [ /* … */ ],
"createdAt": "…",
"updatedAt": "…"
}
]curl -X GET "https://api.chain-monitor.online/v1/wallets" \ -H "Authorization: Bearer api_key_xxx"
POST /v1/wallets
Add a new wallet to monitor.
Request body (JSON)
{
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1",
"networkUuid": "UUID_FROM_GET_PUBLIC_NETWORKS",
"tokenUuids": ["TOKEN_UUID_FROM_NETWORK"],
"minAmount": "100",
"webhookUrl": "https://your-app.com/webhook",
"notifySettings": {
"webhook": true,
"telegram": false,
"notifyIncoming": true,
"notifyOutgoing": true
}
}Example response (JSON)
{
"id": "…",
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1",
"status": "active",
"tokenUuids": ["…"],
"minAmount": "100",
"webhookUrl": "https://your-app.com/webhook",
"notifySettings": { "webhook": true, "telegram": false, "notifyIncoming": true, "notifyOutgoing": true },
"network": { /* … */ },
"tokens": [ /* … */ ],
"createdAt": "…",
"updatedAt": "…"
}curl -X POST "https://api.chain-monitor.online/v1/wallets" \
-H "Authorization: Bearer api_key_xxx" \
-H "Content-Type: application/json" \
-d '{ ... }'PATCH /v1/wallets/:id
Update monitor settings (tokens, min amount, webhook URL).
Optional fields — send only what you want to change.
{
"tokenUuids": ["…"],
"minAmount": "50",
"webhookUrl": "https://example.com/hook",
"notifySettings": { "webhook": true },
"status": "paused"
}DELETE /v1/wallets/:id
Remove a wallet monitor. Associated data is deleted.
curl -X DELETE "https://api.chain-monitor.online/v1/wallets/WALLET_UUID" \ -H "Authorization: Bearer api_key_xxx"
POST /v1/wallets/:id/toggle
Enable or pause monitoring for a wallet.
Example response (JSON)
{ "status": "paused" } // or "active"Notifications
List and fetch notification records using your API key (same filters as the dashboard list).
GET /v1/notifications
Query params: limit (1–100, default 25), offset (default 0), status, channel, networkSlug. Response: { notifications, total }.
Example response (JSON)
{ "notifications": [ { "id": "…", "userId": "…", "walletId": "…", "channel": "webhook", "eventType": "transaction_detected", "status": "sent", "transactionHash": "0x…", "networkSlug": "ethereum", "payload": { }, "createdAt": "…" } ], "total": 42 }curl -G "https://api.chain-monitor.online/v1/notifications" \ --data-urlencode "limit=25" \ --data-urlencode "offset=0" \ -H "Authorization: Bearer api_key_xxx"
GET /v1/notifications/:id
Get a single notification by ID.
The legacy route GET /notifications (without v1) uses the dashboard JWT session only.
Payment pages (API key)
Automate payment pages: add a receiving address to the pool, or create a payment that reserves a free wallet for the requested network.
POST /v1/payment-pages/pool-wallets
Adds one address to the pool of a payment page you own. Use paymentPageUuid from the dashboard when you create a page.
{
"paymentPageUuid": "PAGE_UUID_FROM_DASHBOARD",
"wallet": "0x…",
"networkUuid": "…"
}Example response (JSON)
{
"uuid": "…",
"wallet": "0x…",
"paymentPageUuid": "…",
"status": "free",
"networkUuid": "…",
"network": { "id": "…", "name": "Ethereum", "slug": "ethereum" },
"createdAt": "…",
"updatedAt": "…"
}POST /v1/payment-pages/payments
Reserves the next free wallet in the pool for the given network and records expected amount and token. Returns 400 if no free wallet is available.
{
"paymentPageUuid": "…",
"networkUuid": "…",
"tokenUuid": "…",
"amount": "100.50",
"webHookUrl": "https://optional-callback.example/hook",
"dataOrder": { "orderId": "shop-123" }
}Example response (JSON)
{
"uuid": "…",
"webHookUrl": "https://…",
"status": "waiting",
"wallet": "0x…",
"networkUuid": "…",
"tokenUuid": "…",
"amount": "100.50",
"dataOrder": { "orderId": "shop-123" },
"dataTx": null,
"createdAt": "…",
"updatedAt": "…"
}Webhooks
When a transaction matches your monitor (network, token, min amount), we send a POST request to your webhook URL. The payload includes transaction data (txHash, network, direction, asset, amount, from, to, timestamp). After you create your first API key, a webhook signing secret is issued once (Dashboard → API); we send X-Webhook-Signature so you can verify the body.
HTTP request
Method POST, header Content-Type: application/json, body is a UTF-8 JSON object. Top-level fields: eventType, userId, optional correlationId, ISO8601 timestamp, and data (transaction details). Return HTTP 2xx to accept; other statuses may trigger retries.
Where the signing secret comes from
The secret is shown only once in Dashboard → API when you create your first API key. Format: whsec_ followed by hex. One secret per account—all wallet monitors use it. Rotating an API key does not show the secret again. If you lost it, contact support.
Header X-Webhook-Signature
When a secret is stored for your account, every webhook request includes this header. The value is a string of 64 hexadecimal characters (0-9, a-f): the HMAC-SHA256 digest of the raw body, encoded as hex without a prefix.
How the signature is computed (exactly)
Let rawBody be the exact byte sequence of the request body as received over the wire (before JSON.parse). Let secret be your whsec_… string interpreted as UTF-8.
- signature_hex = HMAC-SHA256(key = secret as UTF-8, message = rawBody)
- X-Webhook-Signature = signature_hex as a lowercase hex string (64 characters).
In Node.js this matches: crypto.createHmac('sha256', secret).update(rawBody).digest('hex') when rawBody is a Buffer or string of the same bytes as the HTTP body.
When the header is missing
If no signing secret has been created for the account yet, we do not send X-Webhook-Signature. After the first API key is created and the secret is stored, new deliveries include the header.
Verifying on your server
Read the raw body from the HTTP layer (Buffer / Uint8Array / string of raw bytes). Do not re-serialize a parsed JSON object—whitespace and key order must match what we sent. Compute the expected hex with the same HMAC. Compare the header to the expected value using a constant-time comparison (e.g. crypto.timingSafeEqual on two Buffers built from the hex strings).
Common mistakes
- Using JSON.stringify(parsedObject) instead of the raw body — the digest will not match.
- Trimming or altering the body before verification.
- Case-sensitive hex compare without normalizing, or string compare instead of timing-safe compare.
Node.js (Express) — verify with raw body
import express from 'express';
import { createHmac, timingSafeEqual } from 'crypto';
const app = express();
app.post(
'/webhook',
express.raw({ type: 'application/json' }),
(req, res) => {
const secret = process.env.CHAIN_MONITOR_WEBHOOK_SECRET; // whsec_...
const rawBody = req.body; // Buffer — must match bytes we sent
const sigHeader = (req.get('x-webhook-signature') || '').trim().toLowerCase();
const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(sigHeader, 'utf8');
const b = Buffer.from(expected, 'utf8');
if (a.length !== b.length || !timingSafeEqual(a, b)) {
return res.status(401).send('invalid signature');
}
const payload = JSON.parse(rawBody.toString('utf8'));
// ... handle payload
return res.status(200).send('ok');
}
);Example JSON body
{
"eventType": "transaction_detected",
"userId": "uuid",
"correlationId": "tx-0x...-timestamp",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"txHash": "0x...",
"network": "Ethereum",
"blockNumber": 19485738,
"direction": "in",
"asset": "USDT",
"amount": "250.00",
"from": "0x...",
"to": "0x...",
"timestamp": "2024-01-15T10:30:00Z",
"txHashLink": "https://etherscan.io/tx/0x...",
"fromLink": "https://etherscan.io/address/0x...",
"toLink": "https://etherscan.io/address/0x..."
}
}Contact
Need help? Use the contact page for integration questions and support.
Email: support@chain-monitor.online
Contact →