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).

request format
$ curl https://api.cryptanio.com/v1/watches \
  -H "Authorization: Bearer ck_live_4t9G…R1SC"
Keep keys server-side. A key grants full account access — never ship it in a mobile app or browser code. To roll a key, create a second one, move your traffic across, then revoke the first: revocation takes effect immediately, so cutting over before you revoke is what keeps you from locking yourself out.

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.

create a watch
$ 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" }
  }'
response · 201 created
{
  "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.

EndpointWhat it does
POST/v1/watchesCreate a watch. Returns 201 with the watch object.
GET/v1/watchesList watches, filterable by chain, status, meta.invoice_id.
GET/v1/watches/:idOne watch with its recent payments.
DELETE/v1/watches/:idStop watching. In-flight transactions are still reported for a short grace period.
GET/v1/paymentsQuery detected payments — by watch, address, tx id or time range.
GET/v1/chainsThe networks this deployment watches, with each one's confirmation rule and token support. No key required.
GET/v1/eventsRe-fetch delivered events (up to 30 days) — your safety net if an endpoint was down.

Request fields

FieldTypeNotes
chainstringOne of bitcoin, ethereum, solana, tron, ton, bsc, base, xrp, dash, zcash. Required. Call GET /v1/chains for the live list.
addressstringThe receiving address. On Solana pass the wallet — associated token accounts are derived and watched automatically.
assetstring"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.
notifystring[]Any of webhook, telegram, email. Default: ["webhook"].
expires_atstringOptional RFC 3339 time. Perfect for invoices — the watch retires itself.
metaobjectUp 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.

HeaderMeaning
X-Event-IdUnique event identifier (ULID). Also in the body as event_id.
X-Event-Typepayment.pending, payment.confirmed or payment.rolled_back.
X-TimestampUnix seconds at signing time — part of the signed material.
X-SignatureHex HMAC-SHA256 over <timestamp>.<body>.
X-AttemptDelivery attempt counter, starting at 1.

Payload

payment.confirmed · application/json
{
  "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.

node.js
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
go
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))
}
Why the timestamp is inside the MAC. Signing the body alone would let an observer replay a captured request forever. With the timestamp bound in, rejecting old timestamps closes the replay window — and the timestamp can't be moved without breaking the signature.

Payment lifecycle

Every payment moves through an explicit state machine, and every transition is exactly one event per channel.

StateMeaningWhen 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.

PlanWatched addressesAlerts / monthEndpointsPrice
Free51,0001$0
Starter10025,0003$19 / mo
Pro1,000250,00010$49 / mo
Business10,0002,000,000Unlimited$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.