How to test and use the websocket on Indexer?

I’ve just set up an Indexer, which is running fine and Kibana shows that data getting collected.

Now I wanted to test the websocket with Postman via ws://MY_IP:8080/subscribe
Connection is successfully in Postman, but even sending a simple command like Ping does not give me any response.

{ "command": "ping" }

What could be wrong? How to test the Indexer? What could be a useful command to test f.e. blocks or transactions?

Connection log from Postman:

Handshake Details

Request URL: http://MYIP:8080/subscribe

Request Method: GET

Status Code: 101 Switching Protocols

▶Request Headers

Sec-WebSocket-Version: 13

Sec-WebSocket-Key: o…Qg==

Connection: Upgrade

Upgrade: websocket

Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

Host: MYIP:8080

▶Response Headers

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Accept: n…7TBfkA=
1 Like

@Blockchain please let us the community know how to use it - there are no details at the documents right now.

1 Like

Hello Maik,

Once you’ve established the WebSocket connection to the node, you still need to subscribe to the specific events you want to receive. Without that subscription message, the node won’t push any updates.

After opening the WebSocket connection to the /subscribe endpoint, you can send the following JSON example:

{
  "addresses": [
    "klv1rgceqsxh3m5jrh3vmdsu5e0dd09fpqn54gftjvll9eu6wdddq05s8ax4nr"
  ],
  "subcribed_types": [
    "transactions",
    "blocks",
    "accounts",
    "user_transaction"
  ]
}

From that point on, the node will start sending the requested events through the WebSocket.

  • The available event types you can subscribe to are exactly the ones shown in subcribed_types:
    • transactions
    • blocks
    • accounts
    • user_transaction
  • The addresses field is used to filter:
    • accounts events
    • user_transaction events
  • transactions events are not filtered by addresses; they will include all transactions.

A typical message from the block event will look like this:

{
  "type": "blocks",
  "data": "eyJoYXNoIjoiOTIyN2JkOGY0OTdiYTBjOWFhMWEyOWM2NmE0YmEzMjY2ZTdkY2FiMDRkOWUwZjQ5NTg4NTgwZTk0ZDI4NzNhZCIsIm5vbmNlIjo1NywicGFyZW50SGFzaCI6IjJjMDcwZjM3ZmZmYjZjZmYzMDgyMGQ4YjgyYTQ4NWEyMGUxMDU4YjdkNzk4ZTU2MzU4OTYzZDRjOWI0Yzg1MDgiLCJ0aW1lc3RhbXAiOjE3NjM3NjQxNTUwMDAsInNsb3QiOjU3LCJlcG9jaCI6MiwiaXNFcG9jaFN0YXJ0IjpmYWxzZSwicHJldkVwb2NoU3RhcnRTbG90IjowLCJzaXplIjozNzMsInNpemVUeHMiOjAsInZpcnR1YWxCbG9ja1NpemUiOjM3MywidHhSb290SGFzaCI6IiIsInRyaWVSb290IjoiYTkxNjdlZDEwZWU5OTBhN2VkZDliY2JhNGNiNzA5NGIwMzczMjAyNGNkNzNhYjFmMjA5NDNmZWFlZTY2ZGVjOSIsInZhbGlkYXRvcnNUcmllUm9vdCI6IjJkNjkzNDk3MzIyNjdiYTg5YjdlMTRmYzU5Y2JjMzM3Yjg5M2M0NjZmZjYyZjBhZmNiNTU5MTYyNmJkM2YxZGYiLCJrYXBwc1RyaWVSb290IjoiZWY3MmYzYjY0ZTcxYmZhMjdiZTNkYWVlYzJmYmJjN2I4MWY3MGVhM2VkMmVjZjVlM2JkOWQ2ZDczNzFlODQ4OCIsInByb2R1Y2VyU2lnbmF0dXJlIjoiMzY1ZTNmODljOTYzODZjYzQ3NjllYjdlYTJjNTA2ZDg0NGU4MzcxNmI3MzlmMDdhNTg3MTQyNDk1YmQzODM1ZGYzMzQxZjAxN2QwYmZkYWI5Nzg1Y2I0ZGJjOTdjMDhlIiwic2lnbmF0dXJlIjoiYjc0NzNmOGFmYWE3ZDhlOWIwNDhkNTFlYjY1Y2ExNzMyZDNkZmYzNzdkODA5ZTFjMTJhYmM3MjA0ZWFkNmFjMTljZThiMDU1N2M1NjFkNTFhN2RmM2ZkMjA3YmUyNzAwIiwicHJldlJhbmRTZWVkIjoiZmY3OWYxMTM4NDkyZjU4Yjk4NDJlMzE4OWE0Nzc0ZjUyZTAzNzYzOWUyMWEyMzE5M2YwMmI0MzA3YjRiOTY1NzZlZTc3OTM5NTg4OGIzNmJlMjBmYjM3ZTJmMGUxNDBhIiwicmFuZFNlZWQiOiIwYTYxMzk0MDIzOTA3YTNlOTc0MjI0Nzg2MzdlMjhiMWNhMGU5MDE1NmZiYzU3M2EwZDgzNWQwOTQ1N2U2YmIwMjQzODJjMGFjMGI5Nzg3Y2U1NWEwMmM1M2JjZDQxOTgiLCJ0eENvdW50IjowLCJ0eEhhc2hlcyI6W10sInZhbGlkYXRvcnMiOlsiM2JkNjBmY2E4YjU5YTAwOWRkOTFhZjZlNTNhODY3YmI1M2RiZGU1MTQ2NjIyYTdlMjM2NzA4NGQ4ZGVlNjViMWM1Y2ExNzMwMjdhOGE2NGY4Zjk2MGIwMzdiMTJiMTE4YjkyMjM0YmYwMjk0YjA4ZDFkOGQ4NTQ5MDkyNGEwODYxNTA0YTQxZmQwYWY0M2E5NmVjYTJkMDRlMzZkODI0ZjRlNDI5Y2QyNGJmYTY3Yzg1ZGI5YjI5NGU0YjFhNTEzIl0sInNvZnR3YXJlVmVyc2lvbiI6ImRlZmF1bHQiLCJjaGFpbklEIjoiNDIwNDIwIiwicmVzZXJ2ZWQiOiIiLCJwcm9kdWNlckJMUyI6IjNiZDYwZmNhOGI1OWEwMDlkZDkxYWY2ZTUzYTg2N2JiNTNkYmRlNTE0NjYyMmE3ZTIzNjcwODRkOGRlZTY1YjFjNWNhMTczMDI3YThhNjRmOGY5NjBiMDM3YjEyYjExOGI5MjIzNGJmMDI5NGIwOGQxZDhkODU0OTA5MjRhMDg2MTUwNGE0MWZkMGFmNDNhOTZlY2EyZDA0ZTM2ZDgyNGY0ZTQyOWNkMjRiZmE2N2M4NWRiOWIyOTRlNGIxYTUxMyJ9"
}

The data field is a JSON object encoded as Base64. Decoding the data above yields:

{
  "hash": "1ce0200301f102ee0c1b6774fa50918a733e1141a2324eeb7b98da7e425f7a10",
  "nonce": 215,
  "parentHash": "c6a1ebcf0a2969b3d3a8ae7fa519e846558ea5b642cb63d6b6e37b797dd86012",
  "timestamp": 1763764787000,
  "slot": 215,
  "epoch": 10,
  "isEpochStart": false,
  "prevEpochStartSlot": 0,
  "size": 375,
  "sizeTxs": 0,
  "virtualBlockSize": 375,
  "txRootHash": "",
  "trieRoot": "4799a06cb2971a199f24bd252dba2998b788a7f9d8bc892a7152e4670c210d40",
  "validatorsTrieRoot": "2de85fec77c7bbe569f9dd728505c347f30c9ffc8618fbfc84095c401877d1f6",
  "kappsTrieRoot": "b97b08d2bec8dce84b8fe4cabde015846994def57dd7f6ccb94a65ceb75f22d9",
  "producerSignature": "62f14ebd5e2b149bd1f44f72fcd3ddfae3d3f2fda3a2384f4262f094e32ecd3b66ed5fcb7c3268e8effdab9007913c99",
  "signature": "45b80350bf630a14f2ac776111fce559e9f6ce950d115913839f6a3991cf481d453aa02386746c6a1e546d136b013e00",
  "prevRandSeed": "c340cc0bc845905861232e1ee92b470407c91ad9a970e3972cd487ac0e6384b53fe81712cf0937a3c5e93da0c8b2e504",
  "randSeed": "e561e2649ce7317a54f483ce2cb9fdf4fda958f98b1dff122bbc6e5a338896e3d9a21f194001bb9a69f99827a2752b96",
  "txCount": 0,
  "txHashes": [],
  "validators": [
    "3bd60fca8b59a009dd91af6e53a867bb53dbde5146622a7e2367084d8dee65b1c5ca173027a8a64f8f960b037b12b118b92234bf0294b08d1d8d85490924a0861504a41fd0af43a96eca2d04e36d824f4e429cd24bfa67c85db9b294e4b1a513"
  ],
  "softwareVersion": "default",
  "chainID": "420420",
  "reserved": "",
  "producerBLS": "3bd60fca8b59a009dd91af6e53a867bb53dbde5146622a7e2367084d8dee65b1c5ca173027a8a64f8f960b037b12b118b92234bf0294b08d1d8d85490924a0861504a41fd0af43a96eca2d04e36d824f4e429cd24bfa67c85db9b294e4b1a513"
}

This is the decoded block data corresponding to the "type": "blocks".

3 Likes

Thanks for clarifying that details, which helps a lot and I could successfully test the websocket which is working fine.

Maybe you can help to understand it with another sample:
Lets say I wanna check a specific wallet address and want to see details like balances, token inside that wallet, rewards, historical transactions and so on.
Maybe “just” a live tracking of a specific wallet about balance changes, incoming/outgoing tx could be another use case.

Is that possible too, or is the use of the Rest API needed for such kind of requests?

For historical information about an account (for example, a full list of past transactions), you need to use the HTTP API. The WebSocket only sends events that occur after you subscribe, so anything that happened before the subscription is not available through WebSocket.

Via WebSocket, you can receive account and transaction–related events in real time:

  • When subscribed to user_transaction events, the node will send a WebSocket message for every transaction where any of the subscribed addresses appears in the transaction.
  • When subscribed to accounts events, the node will send the updated account state whenever there is a change for one of the subscribed addresses.

To subscribe, send this JSON to the /subscribe WebSocket endpoint:

{
  "addresses": [
    "klv1rgceqsxh3m5jrh3vmdsu5e0dd09fpqn54gftjvll9eu6wdddq05s8ax4nr"
  ],
  "subcribed_types": [
    "accounts",
    "user_transaction"
  ]
}

This subscribes you to the accounts and user_transaction events. That means whenever there is:

  • A new transaction involving any of the provided addresses, or
  • A change in the account state for any of those addresses

the node will send a WebSocket event with the corresponding data.

Example of a user_transaction event:

{
  "type": "user_transaction",
  "address": "klv1rgceqsxh3m5jrh3vmdsu5e0dd09fpqn54gftjvll9eu6wdddq05s8ax4nr",
  "hash": "",
  "data": "eyJoYXNoIjoiZjNlODU5YTg3OWE1YTMwM2M3NjZjMTRhMTI1MzBhNDY5ZGViYzYyOGFmYWZhYTA2Y2Q5NWU0YTMxN2UwNjQ4MCIsImJsb2NrTnVtIjoxMDgsInNlbmRlciI6ImtsdjFybXF6OXljNWE0eDR5c2w5a3Y0NTBqemZ1dTdzbmF0NXZxNGgwdzdtMGN3NDRldmprM2NzMGtjamN3Iiwibm9uY2UiOjEsInRpbWVzdGFtcCI6MTc2Mzc2NDM1OTAwMCwia0FwcEZlZSI6NTAwMDAwLCJiYW5kd2lkdGhGZWUiOjEwMDAwMDAsInN0YXR1cyI6InN1Y2Nlc3MiLCJyZXN1bHRDb2RlIjoiT2siLCJ2ZXJzaW9uIjoxLCJjaGFpbklEIjoiNDIwNDIwIiwic2lnbmF0dXJlIjpbIjEwODE3Y2NkNzg5ZTMwODAwZTA4ZDc1ZTZjMzU0NDhkYzczOGNkMjlkYzE0OWE0MjBkNmIyNmUwZjIxMDhlYzgzZDZiY2M3ODUxOTJlZDRjNDEzMjA4ZTliODY4Y2ZlYTk3OGZjMWI1ZTY5NGQ1NWZkNTQyNGMyODE4NjE5MDBmIl0sInNlYXJjaE9yZGVyIjowLCJyZWNlaXB0cyI6W3siY0lEIjoyNTUsInNpZ25lciI6ImtsdjFybXF6OXljNWE0eDR5c2w5a3Y0NTBqemZ1dTdzbmF0NXZxNGgwdzdtMGN3NDRldmprM2NzMGtjamN3IiwidHlwZSI6MTksInR5cGVTdHJpbmciOiJTaWduZWRCeSIsIndlaWdodCI6IjEifSx7ImFzc2V0SWQiOiJLTFYiLCJhc3NldFR5cGUiOiJGdW5naWJsZSIsImNJRCI6MCwiY29sbGVjdGlvbiI6IktMViIsImZyb20iOiJrbHYxcm1xejl5YzVhNHg0eXNsOWt2NDUwanpmdXU3c25hdDV2cTRoMHc3bTBjdzQ0ZXZqazNjczBrY2pjdyIsIm1hcmtldHBsYWNlSWQiOiIiLCJub25jZSI6IiIsIm9yZGVySWQiOiIiLCJ0byI6ImtsdjFyZ2NlcXN4aDNtNWpyaDN2bWRzdTVlMGRkMDlmcHFuNTRnZnRqdmxsOWV1NndkZGRxMDVzOGF4NG5yIiwidHlwZSI6MCwidHlwZVN0cmluZyI6IlRyYW5zZmVyIiwidmFsdWUiOjEwMDAwMDAwMH1dLCJjb250cmFjdCI6W3sidHlwZSI6MCwidHlwZVN0cmluZyI6IlRyYW5zZmVyQ29udHJhY3RUeXBlIiwicGFyYW1ldGVyIjp7ImFzc2V0SWQiOiJLTFYiLCJ0b0FkZHJlc3MiOiJrbHYxcmdjZXFzeGgzbTVqcmgzdm1kc3U1ZTBkZDA5ZnBxbjU0Z2Z0anZsbDlldTZ3ZGRkcTA1czhheDRuciIsImFtb3VudCI6MTAwMDAwMDAwfX1dfQ=="
}

Decoding the Base64 in the data field yields:

{
  "hash": "f3e859a879a5a303c766c14a12530a469debc628afafaa06cd95e4a317e06480",
  "blockNum": 108,
  "sender": "klv1rmqz9yc5a4x4ysl9kv450jzfuu7snat5vq4h0w7m0cw44evjk3cs0kcjcw",
  "nonce": 1,
  "timestamp": 1763764359000,
  "kAppFee": 500000,
  "bandwidthFee": 1000000,
  "status": "success",
  "resultCode": "Ok",
  "version": 1,
  "chainID": "420420",
  "signature": [
    "10817ccd789e30800e08d75e6c35448dc738cd29dc149a420d6b26e0f2108ec83d6bcc785192ed4c413208e9b868cfea978fc1b5e694d55fd5424c281861900f"
  ],
  "searchOrder": 0,
  "receipts": [
    {
      "cID": 255,
      "signer": "klv1rmqz9yc5a4x4ysl9kv450jzfuu7snat5vq4h0w7m0cw44evjk3cs0kcjcw",
      "type": 19,
      "typeString": "SignedBy",
      "weight": "1"
    },
    {
      "assetId": "KLV",
      "assetType": "Fungible",
      "cID": 0,
      "collection": "KLV",
      "from": "klv1rmqz9yc5a4x4ysl9kv450jzfuu7snat5vq4h0w7m0cw44evjk3cs0kcjcw",
      "marketplaceId": "",
      "nonce": "",
      "orderId": "",
      "to": "klv1rgceqsxh3m5jrh3vmdsu5e0dd09fpqn54gftjvll9eu6wdddq05s8ax4nr",
      "type": 0,
      "typeString": "Transfer",
      "value": 100000000
    }
  ],
  "contract": [
    {
      "type": 0,
      "typeString": "TransferContractType",
      "parameter": {
        "assetId": "KLV",
        "toAddress": "klv1rgceqsxh3m5jrh3vmdsu5e0dd09fpqn54gftjvll9eu6wdddq05s8ax4nr",
        "amount": 100000000
      }
    }
  ]
}

In this example, the address klv1rgceqsxh3m5jrh3vmdsu5e0dd09fpqn54gftjvll9eu6wdddq05s8ax4nr is the receiver of the transfer.

Because we also subscribed to the accounts event type, the node sends an additional event with the updated account state for that address:

{
  "type": "accounts",
  "address": "klv1rgceqsxh3m5jrh3vmdsu5e0dd09fpqn54gftjvll9eu6wdddq05s8ax4nr",
  "hash": "",
  "data": "eyJhZGRyZXNzIjoia2x2MXJnY2Vxc3hoM201anJoM3ZtZHN1NWUwZGQwOWZwcW41NGdmdGp2bGw5ZXU2d2RkZHEwNXM4YXg0bnIiLCJub25jZSI6MCwiYmFsYW5jZSI6MTAwMDAwMDAwLCJmcm96ZW5CYWxhbmNlIjowLCJ1bmZyb3plbkJhbGFuY2UiOjAsImFsbG93YW5jZSI6MCwicGVybWlzc2lvbnMiOltdLCJ0aW1lc3RhbXAiOjE3NjM3NjQzNTkwMDB9"
}

Decoding the Base64 data field:

{
  "address": "klv1rgceqsxh3m5jrh3vmdsu5e0dd09fpqn54gftjvll9eu6wdddq05s8ax4nr",
  "nonce": 0,
  "balance": 100000000,
  "frozenBalance": 0,
  "unfrozenBalance": 0,
  "allowance": 0,
  "permissions": [],
  "timestamp": 1763764359000
}

This represents the current account state after the transaction, where the address now has a balance of 100000000 (it previously had 0).

A few important points about account state and token balances:

  • The accounts event is emitted whenever the account changes, and this includes changes that come from token operations.
  • However, the WebSocket accounts payload does not currently show individual token balances. It only exposes fields like balance, frozenBalance, etc., as in the JSON above.
  • This means that if you detect that an account has changed and you want to know which token balances changed and by how much, you still need to query that information via the HTTP API.

In a future node update, we plan to extend the WebSocket accounts events so that they also include token balance changes, but only for the specific tokens that were modified in that event. Until that feature is available, detailed per-token balances must be fetched through the API.

3 Likes

Thank you for this very detailed and simple explanation. :handshake:

Is there a list of all available parameters that can be retrieved via WebSocket?

This would be very helpful and would certainly simplify things for some developers over time.