API documentation
Register the addresses you care about on any of ten networks, then receive an alert for every incoming transaction — as a signed webhook, an API event, a Telegram message, an email, or a Discord or Slack post.
Authentication
Every request carries your API key in the Authorization header. You get two keys: ck_test_… (sandbox — simulated transactions, free) and ck_live_… (production).
$ curl https://api.cryptanio.com/v1/watches \
-H "Authorization: Bearer ck_live_4t9G…R1SC"
Quickstart
One request starts the watching. The next transaction on that address triggers your first alert — typically within seconds of the transaction entering a block.
$ curl -X POST https://api.cryptanio.com/v1/watches \
-H "Authorization: Bearer ck_live_…" \
-H "Content-Type: application/json" \
-d '{
"chain": "ethereum",
"address": "0x7c3A1d4E8b2f6C9a0D5e8F1b4C7a2E5d8B1f9eF2",
"asset": "USDC",
"notify": ["webhook", "telegram"],
"meta": { "invoice_id": "inv_8817" }
}'
{
"id": "wch_01J5WQ4T9GZ3MH8B",
"status": "active",
"chain": "ethereum",
"address": "0x7c3A…9eF2",
"asset": "USDC"
}
Set your webhook endpoint and Telegram chat in the dashboard, or per-request via notify_url. Test everything against the sandbox first: watches created with a ck_test_ key can be triggered manually with POST /v1/simulate.
Watches API
A watch is one address plus one asset on one chain. It's the unit your plan counts. All endpoints live under https://api.cryptanio.com/v1.
| Endpoint | What it does |
|---|---|
| POST/v1/watches | Create a watch. Returns 201 with the watch object. |
| GET/v1/watches | List watches, filterable by chain, status, meta.invoice_id. |
| GET/v1/watches/:id | One watch with its recent payments. |
| DELETE/v1/watches/:id | Stop watching. In-flight transactions are still reported for a short grace period. |
| GET/v1/payments | Query detected payments — by watch, address, tx id or time range. |
| GET/v1/chains | The networks this deployment watches, with each one's confirmation rule and token support. No key required. |
| GET/v1/events | Re-fetch delivered events (up to 30 days) — your safety net if an endpoint was down. |
Request fields
| Field | Type | Notes |
|---|---|---|
| chain | string | One of bitcoin, ethereum, solana, tron, ton, bsc, base, xrp, dash, zcash. Required. Call GET /v1/chains for the live list. |
| address | string | The receiving address. On Solana pass the wallet — associated token accounts are derived and watched automatically. |
| asset | string | "native" (default), a symbol we know (USDC, USDT, DAI), or a contract address — ERC-20, BEP-20, TRC-20, an SPL mint or a jetton master. Bitcoin, Dash, Zcash and XRP carry no tokens we watch, so they take native only. |
| notify | string[] | Any of webhook, telegram, email. Default: ["webhook"]. |
| expires_at | string | Optional RFC 3339 time. Perfect for invoices — the watch retires itself. |
| meta | object | Up to 10 of your own key-value pairs, echoed back in every alert. |
Webhooks
Each state change is one HTTP POST to your endpoint. Delivery is at-least-once: duplicates are possible and expected — deduplicate on (chain, tx_id, index, address, asset), or simply on event_id.
| Header | Meaning |
|---|---|
| X-Event-Id | Unique event identifier (ULID). Also in the body as event_id. |
| X-Event-Type | payment.pending, payment.confirmed or payment.rolled_back. |
| X-Timestamp | Unix seconds at signing time — part of the signed material. |
| X-Signature | Hex HMAC-SHA256 over <timestamp>.<body>. |
| X-Attempt | Delivery attempt counter, starting at 1. |
Payload
{
"event_id": "01J5WQ4T9GZ3MH8B2E7KD6R1SC",
"event_type": "payment.confirmed",
"chain": "ethereum",
"occurred_at": "2026-08-16T13:52:01Z",
"watch_id": "wch_01J5WQ4T9GZ3MH8B",
"meta": { "invoice_id": "inv_8817" },
"payment": {
"tx_id": "0x9b41f2…c77d",
"index": 2,
"height": 20731442,
"address": "0x7c3A…9eF2",
"asset": "USDC",
"contract": "0xA0b8…eB48",
"amount_raw": "250000000",
"decimals": 6,
"amount": "250.00",
"confirmations": 12
}
}
- amount_raw is a string in the asset's minimal units. JSON numbers lose precision above 2⁵³ and can't hold an Ethereum uint256 — parse with a big-integer type. amount is a convenience decimal string, safe for display.
- index separates several credits inside one transaction: a Bitcoin output index, an Ethereum log index, zero on Solana.
- meta echoes whatever you attached to the watch — route events without a database lookup.
Retries
- Timeout per attempt: 10 s. Any non-2xx response, or none, schedules a retry.
- Exponential backoff from 2 s up to 15 min, for 24 hours.
- Missed something anyway? GET /v1/events re-serves the last 30 days.
Verifying signatures
The signature is a hex HMAC-SHA256 over the string <timestamp>.<raw body>, keyed with your webhook secret (dashboard → Webhooks). Verify in constant time and reject stale timestamps to close replays.
const crypto = require("node:crypto");
function verify(secret, timestamp, rawBody, signature) {
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected), Buffer.from(signature));
}
// reject if |now − timestamp| > 300 s, then verify
func verify(secret, ts string, body []byte, sig string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(ts))
mac.Write([]byte("."))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(sig))
}
Payment lifecycle
Every payment moves through an explicit state machine, and every transition is exactly one event per channel.
| State | Meaning | When you receive it |
|---|---|---|
| pending | The transaction is in a block that is not yet irreversible. | Within seconds of inclusion — show progress in your checkout. |
| confirmed | Deep or final enough that the network's own rules say it will not be undone. | Each network's own rule: protocol finality on Ethereum, BNB Chain and Base; a finalized slot on Solana; a solidified block on TRON; a ChainLock on Dash; a validated ledger on XRP; depth on Bitcoin (3) and Zcash (5). Ship goods on this event. |
| rolled_back | The block left the chain in a reorganisation, or the transaction failed. | Only with proof — never inferred from silence. Rare, but this alert is why you can trust the other two. |
A payment reversed by a reorganisation and later re-mined produces a new pending → confirmed sequence with a new revision — your event log stays complete and ordered.
Limits & billing
Plans differ only in volume — features are identical everywhere except SLA and support level. Full pricing on the main page.
| Plan | Watched addresses | Alerts / month | Endpoints | Price |
|---|---|---|---|---|
| Free | 5 | 1,000 | 1 | $0 |
| Starter | 100 | 25,000 | 3 | $19 / mo |
| Pro | 1,000 | 250,000 | 10 | $49 / mo |
| Business | 10,000 | 2,000,000 | Unlimited | $149 / mo |
- A watch = one address + one asset. Active watches count; deleted or expired ones free the slot immediately.
- An alert = one state change on one channel. Retries of the same event are free.
- Over the limit? Alerts keep flowing for 48 hours while we email you — no surprise charges, nothing silently dropped.
- API rate limit: 10 requests/s (Free), 50 requests/s (paid plans). 429 with Retry-After beyond that.
Questions?
Write to hello@cryptanio.com — an engineer answers, usually within a few hours.