Public API · Address Watch
Address Watch
Address Watch lets you subscribe to on-chain addresses and read the events they produce: value that arrived (payment), value that left (payout), and other transactions that touched the address. All routes require signed headers (see Authentication) and use the base URL from the Overview. Success responses carry ok: true plus the fields below; failures carry ok: false, error_code and error. Delivery of the same events to your server (webhook or WebSocket) is configured separately, see Webhooks and WebSockets.
Flow diagram
How an address is watched and how its transactions reach you. You add an address once; every transaction that touches it produces events until you remove it.
Errors
Failed calls answer { ok: false, error_code, error } with an HTTP status. A request that cannot be accepted (wrong body, bad query value) is 400 with an errors list: every field that failed is named in it, and error_code is common.validation_failed when more than one field failed.
watch.network_required and watch.address_required (400): network_slug or address is empty or too long. watch.scan_not_enabled (400): the network key cannot be watched. watch.no_plan (400): the account has no plan.
watch.address_unavailable (403): the plan has no address slots. watch.address_limit (403): all slots are taken. watch.quota (429): too many adds or deletes today, this week or this month.
watch.invalid_event_type (400): event_type is not payment, payout or other. watch.not_found (404): unknown watch_id on delete.
watch.ingest_unavailable (503): temporarily unavailable, retry later. watch.save_failed (500): the watch could not be saved, retry.
Validation error
{
"ok": false,
"error_code": "common.validation_failed",
"error": "Validation failed",
"errors": [
{ "field": "per_page", "error_code": "common.validation_failed", "error": "per_page must be a whole number, from 1 to 100." },
{ "field": "before_id", "error_code": "common.validation_failed", "error": "before_id must be a uuid." }
]
}GET /v1/address-watches/account
Address slots of the plan: how many there are and how many are taken, plus the webhook state.
Campos de la respuesta
| Nombre | Tipo | Descripción |
|---|---|---|
| account.webhook_address_slots | number | Plan cap for concurrently watched addresses. 0 means Address Watch is not available on the plan. |
| account.address_slots_used | number | Number of active watched addresses. |
| account.webhook_url | string | Configured webhook URL. Absent when none is set. (opcional) |
| account.webhook_enabled | boolean | Whether outbound webhook delivery is enabled (false when no webhook is configured). |
- Read-only summary. To change the webhook URL, secret or event networks use the Webhooks article (GET/PUT /v1/webhooks).
Ejemplo de solicitud
import { createHash, createHmac } from 'node:crypto'
const API_KEY = process.env.PUBLIC_API_KEY! // cabinet UUID
const SECRET = process.env.PUBLIC_API_SECRET! // sk_live_…
const BASE = 'https://api.rpcnode.dev'
// path: as the server receives it, with the query; body: the exact string that is sent
async function call(method: string, path: string, body = '') {
const timestamp = Math.floor(Date.now() / 1000)
const bodyHash = createHash('sha256').update(body).digest('hex')
const signature = createHmac('sha256', SECRET)
.update([timestamp, method, path, bodyHash].join('\n'))
.digest('hex')
const res = await fetch(BASE + path, {
method,
headers: {
'X-Api-Key': API_KEY,
'X-Timestamp': String(timestamp),
'X-Signature': signature,
...(body ? { 'Content-Type': 'application/json' } : {}),
},
body: body || undefined,
})
return res.json()
}
console.log(await call('GET', '/v1/address-watches/account'))La solicitud se firma en el propio fragmento de código; consulta Authentication para ver las reglas de firma.
Ejemplo de respuesta
{
"ok": true,
"account": {
"webhook_address_slots": 10,
"address_slots_used": 2,
"webhook_url": "https://example.com/hooks/rpcnode",
"webhook_enabled": true
}
}GET /v1/address-watches
List watched addresses (paginated, newest first). Filter by network and/or part of the address.
Parámetros de consulta
| Nombre | Tipo | Descripción |
|---|---|---|
| network_slug | string | Exact watch network key (trimmed, lowercased), e.g. ethereum or ethereum-sepolia. Max 255. (opcional) |
| address | string | Case-insensitive substring of the address (not only a prefix), after the same normalization as add. Max 255. (opcional) |
| page | integer | Page number, 1–10000 (default 1). (opcional) |
| per_page | integer | Page size, 1–100 (default 20). (opcional) |
Campos de la respuesta
| Nombre | Tipo | Descripción |
|---|---|---|
| items[].watch_id | string | Public watch identifier (uuid). Use it to delete the watch and to filter events. |
| items[].network_slug | string | Watch network key: the base slug on mainnet (ethereum), {network}-{env} elsewhere (ethereum-sepolia). |
| items[].address | string | Watched address in its normalized form (see the add method). |
| items[].status | string | Watch status: active or stopped. |
| items[].created_at | string | Creation time, ISO 8601 with offset. |
| pagination.page | number | Current page. |
| pagination.per_page | number | Page size used for this response. |
| pagination.total | number | Total number of matching rows. |
| pagination.has_more | boolean | True if more pages exist after this one. |
- Sign the path together with the query string (see Authentication): the query is part of the signature.
- A page, per_page or text value outside its bounds is rejected with 400 and an errors list naming the parameter; it is never replaced by a default.
- Only active and stopped watches are listed; deleted ones are not.
Ejemplo de solicitud
import { createHash, createHmac } from 'node:crypto'
const API_KEY = process.env.PUBLIC_API_KEY! // cabinet UUID
const SECRET = process.env.PUBLIC_API_SECRET! // sk_live_…
const BASE = 'https://api.rpcnode.dev'
// path: as the server receives it, with the query; body: the exact string that is sent
async function call(method: string, path: string, body = '') {
const timestamp = Math.floor(Date.now() / 1000)
const bodyHash = createHash('sha256').update(body).digest('hex')
const signature = createHmac('sha256', SECRET)
.update([timestamp, method, path, bodyHash].join('\n'))
.digest('hex')
const res = await fetch(BASE + path, {
method,
headers: {
'X-Api-Key': API_KEY,
'X-Timestamp': String(timestamp),
'X-Signature': signature,
...(body ? { 'Content-Type': 'application/json' } : {}),
},
body: body || undefined,
})
return res.json()
}
console.log(await call('GET', '/v1/address-watches'))La solicitud se firma en el propio fragmento de código; consulta Authentication para ver las reglas de firma.
Ejemplo de respuesta
{
"ok": true,
"items": [
{
"watch_id": "c9fd242d-525b-4b81-9a99-b2a95be331c9",
"network_slug": "ethereum",
"address": "0x28c6c06298d514db089934071355e5743bf21d60",
"status": "active",
"created_at": "2026-08-22T10:15:00+00:00"
}
],
"pagination": {
"page": 1,
"per_page": 20,
"total": 1,
"has_more": false
}
}POST /v1/address-watches
Start watching an address on a network. Takes one address slot.
Cuerpo de la solicitud
| Nombre | Tipo | Descripción |
|---|---|---|
| network_slug | string | Watch network key (max 80, trimmed, lowercased). Mainnet is the base network slug (ethereum); other environments are {network}-{env} (ethereum-sepolia). The environment must be available for watching, otherwise the request fails with watch.scan_not_enabled. |
| address | string | Address to watch (max 255, trimmed). 0x… addresses are lowercased (32-byte addresses such as Aptos/Sui/Starknet are written with all 64 digits); other encodings (Tron T…, Bitcoin, Solana) keep their case. |
Campos de la respuesta
| Nombre | Tipo | Descripción |
|---|---|---|
| address | object | The created watch: the same fields as a list item. |
| address.watch_id | string | Public watch identifier (uuid). Use it to delete the watch and to filter events. |
| address.network_slug | string | Watch network key: the base slug on mainnet (ethereum), {network}-{env} elsewhere (ethereum-sepolia). |
| address.address | string | Watched address in its normalized form (see the add method). |
| address.status | string | Watch status: active or stopped. |
| address.created_at | string | Creation time, ISO 8601 with offset. |
- Body fields are exact: a misspelled or unknown field is rejected with 400 and named in errors. Send Content-Type: application/json and sign the raw body.
- Adding an address that is already watched is not an error: the existing active watch is returned and no extra slot is taken. Adding an address again after deleting it takes a slot again.
- Adds and deletes are rate-limited per account (day, week and month budgets that grow with the number of slots); over the budget the call fails with 429 watch.quota.
Ejemplo de solicitud
import { createHash, createHmac } from 'node:crypto'
const API_KEY = process.env.PUBLIC_API_KEY! // cabinet UUID
const SECRET = process.env.PUBLIC_API_SECRET! // sk_live_…
const BASE = 'https://api.rpcnode.dev'
// path: as the server receives it, with the query; body: the exact string that is sent
async function call(method: string, path: string, body = '') {
const timestamp = Math.floor(Date.now() / 1000)
const bodyHash = createHash('sha256').update(body).digest('hex')
const signature = createHmac('sha256', SECRET)
.update([timestamp, method, path, bodyHash].join('\n'))
.digest('hex')
const res = await fetch(BASE + path, {
method,
headers: {
'X-Api-Key': API_KEY,
'X-Timestamp': String(timestamp),
'X-Signature': signature,
...(body ? { 'Content-Type': 'application/json' } : {}),
},
body: body || undefined,
})
return res.json()
}
console.log(await call('POST', '/v1/address-watches', '{"network_slug":"string","address":"string"}'))La solicitud se firma en el propio fragmento de código; consulta Authentication para ver las reglas de firma.
Ejemplo de respuesta
{
"ok": true,
"address": {
"watch_id": "c9fd242d-525b-4b81-9a99-b2a95be331c9",
"network_slug": "tron",
"address": "TU4vEruvZwLLkSfV9bNw12EJTPvNr7Pvaa",
"status": "active",
"created_at": "2026-08-22T10:15:00+00:00"
}
}DELETE /v1/address-watches/{watchId}
Stop watching an address. The watch id goes in the path; there is no request body.
- watchId is the watch_id returned by add or list (a uuid). An unknown, foreign or malformed id returns 404 watch.not_found.
- Deleting frees the slot at once, but counts against the delete budget (429 watch.quota when exceeded).
- Events already recorded for the watch stay readable through the events route.
Ejemplo de solicitud
import { createHash, createHmac } from 'node:crypto'
const API_KEY = process.env.PUBLIC_API_KEY! // cabinet UUID
const SECRET = process.env.PUBLIC_API_SECRET! // sk_live_…
const BASE = 'https://api.rpcnode.dev'
// path: as the server receives it, with the query; body: the exact string that is sent
async function call(method: string, path: string, body = '') {
const timestamp = Math.floor(Date.now() / 1000)
const bodyHash = createHash('sha256').update(body).digest('hex')
const signature = createHmac('sha256', SECRET)
.update([timestamp, method, path, bodyHash].join('\n'))
.digest('hex')
const res = await fetch(BASE + path, {
method,
headers: {
'X-Api-Key': API_KEY,
'X-Timestamp': String(timestamp),
'X-Signature': signature,
...(body ? { 'Content-Type': 'application/json' } : {}),
},
body: body || undefined,
})
return res.json()
}
console.log(await call('DELETE', '/v1/address-watches/00000000-0000-0000-0000-000000000000'))La solicitud se firma en el propio fragmento de código; consulta Authentication para ver las reglas de firma.
Ejemplo de respuesta
{
"ok": true
}GET /v1/address-watches/events
Recent events of your watched addresses, newest first. The transaction details are flattened onto each event (no nested payload).
Parámetros de consulta
| Nombre | Tipo | Descripción |
|---|---|---|
| watch_id | string | Only events of this watch. Must be a uuid. (opcional) |
| network_slug | string | Exact watch network key (lowercased), e.g. tron. Max 255. (opcional) |
| address | string | Case-insensitive prefix of the event address, after address normalization. Max 255. (opcional) |
| event_type | string | payment, payout or other. Anything else returns 400 watch.invalid_event_type. (opcional) |
| tx_hash | string | Case-insensitive prefix of the transaction hash. Max 255. (opcional) |
| before_id | string | Cursor: the id (uuid) of an event; only events older than it are returned. An id that matches no event returns an empty page. (opcional) |
| page | integer | Page number, 1–10000 (default 1). (opcional) |
| per_page | integer | Page size, 1–100 (default 50). (opcional) |
Campos de la respuesta
| Nombre | Tipo | Descripción |
|---|---|---|
| items[].id | string | Event id (uuid). Use it as before_id. |
| items[].watch_id | string | Watch the event belongs to (uuid). |
| items[].network_slug | string | Watch network key. |
| items[].address | string | Watched address the event is about. |
| items[].tx_hash | string | Transaction hash. |
| items[].block_height | number | Block height of the transaction; 0 while it is only in the mempool. |
| items[].created_at | string | When the event was recorded, ISO 8601 with offset. |
| items[].schema_version | string | Event format version, always "2". |
| items[].network | string | Network slug of the event. |
| items[].type | string | payment (value came to the address), payout (value left it) or other. |
| items[].direction | string | in, out or self, relative to address. |
| items[].is_payment | boolean | True for payment and payout, false for other. |
| items[].from_address | string | Sender. |
| items[].to_address | string | Recipient. |
| items[].token | object | null | { contract, standard, symbol, decimals } for a token transfer, null for the native coin. symbol and decimals are null when the token is not in the network catalog. |
| items[].currency | string | null | Ticker (USDT, or the native coin such as TRX). null when unknown. |
| items[].amount | string | null | Human-readable amount as a decimal string ("30"). null when the ticker or decimals are unknown; use amount_raw then. |
| items[].amount_raw | string | null | Amount in the smallest unit, as a string. |
| items[].fee | string | null | Network fee in the native coin. |
| items[].fee_raw | string | null | Network fee in the smallest unit. |
| items[].status | string | Transaction status as seen when the event was recorded (check, success or failed). For progress use confirmation_status. |
| items[].method | string | null | Contract method (transfer…); null for a native transfer. |
| items[].block_hash | string | null | Block hash; null while in the mempool. |
| items[].block_time | number | null | Block time, Unix seconds; null while in the mempool. |
| items[].risk | array | Risk flags. Currently always an empty array. |
| items[].confirmation_status | string | pending (mempool), seen (in a block), confirming, finalized or failed. Updated as the transaction progresses. |
| items[].confirmations | number | Confirmations counted so far (0 while pending). |
| items[].required_confirmations | number | Confirmations this account needs on this network. |
| items[].final_block | number | Block height at which the transaction is final (block_height + required_confirmations - 1); 0 while pending. |
| items[].confirmed | boolean | True only when confirmation_status is finalized. |
| pagination.page | number | Current page. |
| pagination.per_page | number | Page size used for this response. |
| pagination.total | number | Total number of matching rows. |
| pagination.has_more | boolean | True if more pages exist after this one. |
- Sign the path together with the query string (see Authentication).
- This is a recent log, not an archive. Use a webhook or WebSocket for delivery.
- The id of an event is a uuid and is stable: the same transaction event always has the same id. To read the log page by page without gaps while new events arrive, pass the id of the last event you received as before_id instead of increasing page.
- Empty or unknown values are never written as empty strings: unknown fields are null or absent (see the field list). The set of keys after created_at follows the Address Watch event format (schema_version "2"), the same keys as the webhook body.
Ejemplo de solicitud
import { createHash, createHmac } from 'node:crypto'
const API_KEY = process.env.PUBLIC_API_KEY! // cabinet UUID
const SECRET = process.env.PUBLIC_API_SECRET! // sk_live_…
const BASE = 'https://api.rpcnode.dev'
// path: as the server receives it, with the query; body: the exact string that is sent
async function call(method: string, path: string, body = '') {
const timestamp = Math.floor(Date.now() / 1000)
const bodyHash = createHash('sha256').update(body).digest('hex')
const signature = createHmac('sha256', SECRET)
.update([timestamp, method, path, bodyHash].join('\n'))
.digest('hex')
const res = await fetch(BASE + path, {
method,
headers: {
'X-Api-Key': API_KEY,
'X-Timestamp': String(timestamp),
'X-Signature': signature,
...(body ? { 'Content-Type': 'application/json' } : {}),
},
body: body || undefined,
})
return res.json()
}
console.log(await call('GET', '/v1/address-watches/events'))La solicitud se firma en el propio fragmento de código; consulta Authentication para ver las reglas de firma.
Ejemplo de respuesta
{
"ok": true,
"items": [
{
"id": "3f0c1a52-7a4e-3d0f-9a1b-52c7f4a8e611",
"watch_id": "c9fd242d-525b-4b81-9a99-b2a95be331c9",
"network_slug": "tron",
"address": "TU4vEruvZwLLkSfV9bNw12EJTPvNr7Pvaa",
"tx_hash": "bb08e86bca3219613b2d10f8b731c139aebc74c7a85f2a736b18bed50a2c320c",
"block_height": 85558636,
"created_at": "2026-08-22T10:15:03+00:00",
"schema_version": "2",
"network": "tron",
"block_hash": "0000000005193f6c0f1e2b6d5c4e8a7b1d3c9e2f4a6b8c0d1e2f3a4b5c6d7e8f",
"block_time": 1787393700,
"from_address": "TU4vEruvZwLLkSfV9bNw12EJTPvNr7Pvaa",
"to_address": "TH7uGJPN4Dy8XBBNkSuPtiZL1QMTB7jsJv",
"type": "payout",
"token": { "contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", "symbol": "USDT", "standard": "trc20", "decimals": 6 },
"currency": "USDT",
"amount": "30",
"amount_raw": "30000000",
"fee": "13.3",
"fee_raw": "13300000",
"status": "check",
"method": "transfer",
"is_payment": true,
"direction": "out",
"risk": [],
"confirmation_status": "finalized",
"required_confirmations": 19,
"confirmations": 19,
"final_block": 85558654,
"confirmed": true
}
],
"pagination": {
"page": 1,
"per_page": 50,
"total": 1,
"has_more": false
}
}Consejos
- network_slug is the watch network key: the base catalog slug on mainnet (ethereum, tron) and {network}-{env} on other environments (ethereum-sepolia, tron-nile). Only environments available for watching can be used.
- DELETE takes the watch id in the path (/v1/address-watches/{watchId}) and no body. The old body-based form no longer exists.
- Fields without a value are not written. Read an absent field as "no value", for example account.webhook_url when no webhook is configured.
- The query string is part of the signed path, so sign exactly the path and query you send.
- Event delivery (webhook URL, secret, which networks fire) is configured with GET/PUT /v1/webhooks, not here. See the Webhooks article; for a live socket see WebSockets.