Docs
v1

Guide

WebSocket events

Follow finalized cuts, addresses, the mempool and single transactions live over the three Tenrai WebSocket endpoints, and recover after a reconnect.

On this page

Three sockets push what would otherwise need polling. All of them speak JSON text frames of the form {"type": …, "data": …} and name their subprotocol when connecting.

PathFor
/v1/eventsEvery finalized cut, in order, resumable. For indexers and payment systems. Subprotocol tenrai.node-events.v1
/v1/wsSubscriptions by topic, and read requests. For apps and wallets. Subprotocol tenrai.node-api.v1
/v1/transactions/{id}/status/streamOne transaction until it is final. Subprotocol tenrai.transaction-status.v1
JavaScript
const socket = new WebSocket("wss://api.tenrai-technologies.org/v1/ws", "tenrai.node-api.v1");

Finalized cuts

/v1/events starts with a checkpoint, the finalized head, and then sends one cut_finalized per height:

JSON
{
  "type": "cut_finalized",
  "data": {
    "api_version": 1,
    "cut": { "height": 20139, "cut_id": "80bd765b…", "state_root": "6319bd1d…", "validator_set_id": "cce43cf4…" },
    "registered_work": 68,
    "executed_work": 71,
    "rewards": [
      { "block_id": "0b45968f…", "transaction_id": "c46c371e…", "output_index": 0, "amount": "41118422" }
    ],
    "stake_rewards": "781494251",
    "stake_payouts": []
  }
}

Resume after a reconnect

Store the height of the last cut you handled and reconnect with it:

Text
/v1/events?after_height=20139

The server first replays the cuts above that height, then continues live. The replay reaches one hour back. If your height is older, the stream sends resync_required with the reason history_unavailable and closes: read the missed range over HTTP, for example with POST /v1/address/changes, then connect again without the parameter.

resync_required reasonMeaning
stream_laggedYou read too slowly and events were dropped
stream_restartedThe source restarted
history_unavailableafter_height is older than the replay reaches

Subscriptions

On /v1/ws you send frames with an id of your choice and get the answer under the same id.

JSONYou send
{"type":"subscribe","data":{"id":1,"topic":"cuts"}}
{"type":"subscribe","data":{"id":2,"topic":"addresses","addresses":["trai1…"]}}
{"type":"subscribe","data":{"id":3,"topic":"transaction","transaction_id":"…"}}
{"type":"request","data":{"id":4,"path":"/v1/chain/head"}}
{"type":"unsubscribe","data":{"id":5,"subscription":1}}
{"type":"ping","data":{"id":6}}
JSONYou receive
{"type":"subscribed","data":{"id":1,"subscription":1,"topic":"cuts"}}
{"type":"response","data":{"id":4,"status":200,"body":{…}}}
{"type":"unsubscribed","data":{"id":5,"subscription":1}}
{"type":"pong","data":{"id":6}}
FrameAnswer
subscribesubscribed with the subscription number, then notification frames
requestresponse with status and body, for any GET route
unsubscribeunsubscribed
pingpong

A notification names its subscription and carries one event:

JSON
{
  "type": "notification",
  "data": {
    "subscription": 1,
    "event": {
      "type": "cut_finalized",
      "data": {
        "cut": { "height": 20126, "cut_id": "1764a581…", "state_root": "5876d641…", "validator_set_id": "cce43cf4…" },
        "registered_blocks": 53,
        "executed_blocks": 68,
        "rewards": "781507821",
        "stake_rewards": "781494251",
        "finalized_at_millis": 1791346785721
      }
    }
  }
}

Topics

TopicEventsData
cutscut_finalizedThe cut, its block counts and rewards
blocksblock_acceptedBlock ID, parents, transactions, reward address, miner tag
mempoolmempoolThe /v1/mempool document
ratesratesThe /v1/chain/rates document
syncsyncThe /v1/node/sync document
transactiontransaction_statusThe status document. Takes one transaction_id
addressesaddress_changed, mempool_tx_seen, mempool_tx_removedReceived and spent outputs with the new balance; pending payments. Takes up to 64 addresses
mempool_transactionsmempool_transaction, mempool_transaction_removedEvery mempool transaction. Not offered by every service

mempool_tx_seen and mempool_tx_removed describe payments that are still pending. They are what this server sees and never part of a balance. mempool_tx_removed gives the reason: finalized, expired, evicted, conflict, rejected or unknown. The finalized change arrives as address_changed.

When events were missed

{"type":"resync_required","data":{"subscription":1,"reason":"stream_lagged"}} means the subscription lost events. It stays open; read the current state over HTTP and carry on. Do the same after every reconnect, since nothing is replayed on this socket.

One transaction

/v1/transactions/{id}/status/stream sends a snapshot with the current status document and an update on every change, until the status is final.

Limits

Per socketNodeTenrai API
Subscriptions6464
Watched addresses512512
Frame you send64 KiB16 KiB
Answer to a request256 KiB1 MiB
Frames per second10010
Requests in flight84

The Tenrai API column shows its limits. A request path may be 1 KiB long. Frames are text; a binary frame ends the connection. Larger answers need HTTP.

Socket frames are not signed. Read anything you must be able to prove over HTTP with a signed answer.

With the SDK

JavaScript
const live = tenrai.live();
live.subscribe({
  topic: "addresses",
  addresses: [address],
  onEvent: (event) => console.log(event.type, event.data),
  onResync: () => reloadBalance(),
});

The SDK reconnects with growing delays, restores subscriptions and calls onResync when events may have been missed.

Type to search guides, concepts and reference pages.