Copyright © 2025 Dankest, LLC

XChain Explorer WebSocket API Reference

The xchain-explorer provides a WebSocket API for real-time streaming of blockchain events. Clients connect to a coin-scoped endpoint and subscribe to channels with optional filters. The server pushes events as they are indexed — no polling required.

Connection

URL Pattern

ws://host:8080/{COIN}/api/websocket
wss://host:8081/{COIN}/api/websocket

The {COIN} segment determines the chain and network for the connection:

Coin Prefix Chain Network
BTC Bitcoin mainnet
TBTC Bitcoin testnet
RBTC Bitcoin regtest
LTC Litecoin mainnet
TLTC Litecoin testnet
RLTC Litecoin regtest
DOGE Dogecoin mainnet
TDOGE Dogecoin testnet
RDOGE Dogecoin regtest

Example

# Connect with wscat
wscat -c ws://localhost:8080/BTC/api/websocket

WELCOME Message

Sent automatically on connection. Provides server info, current state, limits, and supported features.

{
  "type": "WELCOME",
  "chain": "BTC",
  "network": "mainnet",
  "timestamp": 1743638400000,
  "data": {
    "version": "1.11.0",
    "server_time": 1743638400000,
    "latest_block_index": 890122,
    "latest_action_index": 45677,
    "limits": {
      "max_subscriptions": 25,
      "max_message_rate": 10,
      "max_message_size": 1024,
      "max_connections_per_ip": 5
    },
    "channels": ["blocks", "actions", "mempool", "network", "address", "token", "market", "dispenser"],
    "types": ["ORDER", "ORDER_MATCH", "ORDER_EXPIRE", "COINPAY", "..."],
    "features": ["snapshot", "once", "fields", "statuses", "ticks", "batch", "catch_up"]
  }
}

Use latest_block_index and latest_action_index to seed your local state for catch-up on reconnect.


Client-to-Server Messages

All client messages are JSON with an action field. An optional id field enables request-response correlation — the server echoes it back in the corresponding response.

subscribe

{
  "action": "subscribe",
  "id": "sub-1",
  "channels": ["blocks", "actions"],
  "params": {
    "types": ["SEND", "ORDER_MATCH"],
    "statuses": ["pending_coinpay"],
    "ticks": ["PEPE", "XCHAIN"],
    "fields": ["action_index", "source", "amount"],
    "snapshot": true,
    "once": true,
    "since_action_index": 45677
  }
}
Field Type Required Description
action string yes "subscribe"
id string no Echoed back in SUBSCRIBED response
channels string[] yes Channels to subscribe to
params object no Filter and subscription options

Params:

Param Type Description
types string[] Only receive events matching these action types. Omit for all.
statuses string[] Only receive events matching these statuses. Omit for all.
ticks string[] Only receive events involving these token tickers (global actions channel).
fields string[] Only include these keys in the data payload. Envelope fields always included.
snapshot boolean Send current entity state immediately on subscribe.
once boolean Auto-unsubscribe after the first matching event.
since_action_index number Replay missed events since this action_index (for catch-up on reconnect).

Entity params (required for entity channels):

Channel Singular Batch (plural)
address "address": "1abc..." "addresses": ["1abc...", "1def..."]
token "tick": "PEPE" (use ticks array)
market "tick1": "PEPE", "tick2": "BTC" "pairs": [["PEPE","BTC"], ["XCHAIN","BTC"]]
dispenser "action_index": 12345 "action_indexes": [12345, 12346]

unsubscribe

{
  "action": "unsubscribe",
  "channels": ["address"],
  "params": { "address": "1abc..." }
}

list_subscriptions

{
  "action": "list_subscriptions",
  "id": "debug-1"
}

Returns a SUBSCRIPTION_LIST event with all active subscriptions and their filters.

ping

{
  "action": "ping"
}

Returns a pong event. This is an application-level keepalive for aggressive proxies. The server also sends WebSocket-level ping frames every 30 seconds automatically.


Server-to-Client Events

All events share a common envelope:

{
  "type": "EVENT_TYPE",
  "chain": "BTC",
  "network": "mainnet",
  "timestamp": 1743638400000,
  "data": { }
}

Global Events

NEW_BLOCK

Channel: blocks

{
  "type": "NEW_BLOCK",
  "data": {
    "block_index": 890123,
    "block_hash": "00000000...",
    "block_time": 1743638400,
    "tx_count": 2341,
    "action_count": 7
  }
}

NEW_ACTION

Channel: actions

{
  "type": "NEW_ACTION",
  "data": {
    "action_index": 45678,
    "action": "SEND",
    "tx_hash": "abc123...",
    "block_index": 890123,
    "source": "1abc...",
    "destination": "1def...",
    "status": "valid"
  }
}

NETWORK_STATS

Channel: network

Emitted with each new block.

{
  "type": "NETWORK_STATS",
  "data": {
    "block_height": 890123,
    "total_actions": 45678
  }
}

Entity Events

ADDRESS_UPDATE

Channel: address

{
  "type": "ADDRESS_UPDATE",
  "data": {
    "address": "1abc...",
    "balances": [
      { "tick": "XCHAIN", "amount": "1000.00000000" },
      { "tick": "PEPE", "amount": "50000.00000000" }
    ],
    "last_action_index": 45680
  }
}

TOKEN_UPDATE

Channel: token

{
  "type": "TOKEN_UPDATE",
  "data": {
    "tick": "PEPE",
    "supply": "100000000.00000000",
    "holders": 1234,
    "last_action_index": 45680
  }
}

MARKET_UPDATE

Channel: market

{
  "type": "MARKET_UPDATE",
  "data": {
    "tick1": "PEPE",
    "tick2": "BTC",
    "last_price": "0.00000020",
    "volume_24h": "5000000.00000000",
    "bid": "0.00000019",
    "ask": "0.00000021"
  }
}

DISPENSER_UPDATE

Channel: dispenser

{
  "type": "DISPENSER_UPDATE",
  "data": {
    "action_index": 12345,
    "source": "1abc...",
    "status": "open",
    "give_remaining": "49000.00000000",
    "give_tick": "PEPE"
  }
}

Order Lifecycle Events

These fire on both the actions channel and the address channel for involved addresses.

ORDER_MATCH

{
  "type": "ORDER_MATCH",
  "data": {
    "action_index": 45700,
    "settlement_type": "coinpay",
    "status": "pending_coinpay",
    "source": "1abc...",
    "tx_hash": "def456..."
  }
}

COINPAY_REQUIRED

Emitted when an ORDER_MATCH has settlement_type = coinpay. Contains the obligation details needed to construct a COINPAY transaction.

{
  "type": "COINPAY_REQUIRED",
  "data": {
    "obligation_action_index": 45700,
    "order_match_action_index": 45700,
    "payer_address": "1BotAddr...",
    "payee_address": "1SellerAddr...",
    "coin_amount": "0.01000000",
    "expiration": 1743642100
  }
}

COINPAY_FULFILLED

{
  "type": "COINPAY_FULFILLED",
  "data": {
    "action_index": 45720,
    "tx_hash": "abc123...",
    "source": "1BotAddr...",
    "status": "valid"
  }
}

COINPAY_EXPIRED

{
  "type": "COINPAY_EXPIRED",
  "data": {
    "action_index": 45730,
    "source": "1BotAddr...",
    "status": "valid"
  }
}

ORDER_EXPIRED

{
  "type": "ORDER_EXPIRED",
  "data": {
    "action_index": 45740,
    "source": "1abc...",
    "status": "valid"
  }
}

Swap Lifecycle Events

SWAP_MATCH

{
  "type": "SWAP_MATCH",
  "data": {
    "action_index": 45850,
    "source": "1abc...",
    "status": "valid"
  }
}

SWAP_EXPIRED

{
  "type": "SWAP_EXPIRED",
  "data": {
    "action_index": 45860,
    "source": "1abc...",
    "status": "valid"
  }
}

Dispenser Lifecycle Events

DISPENSE

{
  "type": "DISPENSE",
  "data": {
    "action_index": 45800,
    "source": "1BuyerAddr...",
    "status": "valid"
  }
}

DISPENSER_CLOSED / DISPENSER_EXPIRED

{
  "type": "DISPENSER_CLOSED",
  "data": {
    "action_index": 45900,
    "source": "1OwnerAddr...",
    "status": "valid"
  }
}

System Events

SUBSCRIBED

Sent on every successful subscribe. Echoes the id if provided.

{
  "type": "SUBSCRIBED",
  "id": "sub-1",
  "data": {
    "channel": "address",
    "address": "1abc...",
    "active_filters": {
      "types": ["ORDER_MATCH", "COINPAY_REQUIRED"],
      "statuses": null,
      "ticks": null,
      "fields": null,
      "once": false
    }
  }
}

SUBSCRIPTION_LIST

Response to list_subscriptions.

{
  "type": "SUBSCRIPTION_LIST",
  "id": "debug-1",
  "data": {
    "count": 2,
    "limit": 25,
    "subscriptions": [
      { "channel": "blocks", "filters": { "types": null, "statuses": null, "once": false } },
      { "channel": "address", "address": "1abc...", "filters": { "types": ["ORDER_MATCH"], "statuses": null, "once": false } }
    ]
  }
}

SNAPSHOT

Sent when subscribing with snapshot: true. Contains current state of the subscribed entity.

{
  "type": "SNAPSHOT",
  "data": {
    "channel": "address",
    "address": "1abc...",
    "balances": [{ "tick": "XCHAIN", "amount": "1000.00000000" }],
    "last_action_index": 45680
  }
}

CATCH_UP_COMPLETE

Sent after all catch-up events have been replayed.

{
  "type": "CATCH_UP_COMPLETE",
  "data": {
    "events_replayed": 5,
    "latest_action_index": 45690,
    "truncated": false
  }
}

Catch-up events have "catch_up": true in the envelope to distinguish them from live events.

UNSUBSCRIBED

Sent when a once: true subscription fires.

{
  "type": "UNSUBSCRIBED",
  "data": {
    "channel": "address:1abc...",
    "reason": "once"
  }
}

pong

Response to client ping.

{
  "type": "pong",
  "timestamp": 1743638410000,
  "data": {}
}

error

{
  "type": "error",
  "id": "sub-1",
  "data": {
    "code": "INVALID_TYPE",
    "message": "Unknown action type: FOOBAR"
  }
}

Error Codes

Code Meaning
INVALID_ACTION Unrecognized client action or malformed JSON
INVALID_CHANNEL Unknown channel name or missing entity params
INVALID_CHAIN Unsupported coin prefix in connection URL
INVALID_TYPE Unknown action type in types filter
SUBSCRIPTION_LIMIT Exceeded max 25 subscriptions per connection
RATE_LIMITED Client sending more than 10 messages/sec
CATCH_UP_TOO_OLD since_action_index is more than 1,000 actions behind current
CATCH_UP_IN_PROGRESS A catch-up request is already running for this client

Supported Action Types for types Filter

Category Values
Orders ORDER, ORDER_MATCH, ORDER_EXPIRE
COINPay COINPAY, COINPAY_EXPIRE
Swaps SWAP, SWAP_MATCH, SWAP_EXPIRE
Dispensers DISPENSER, DISPENSE, DISPENSER_CLOSE, DISPENSER_EXPIRE
Transfers SEND, SWEEP, AIRDROP, DIVIDEND
Tokens ISSUE, MINT, DESTROY
Other BROADCAST, CALLBACK, FILE, MESSAGE, LIST, LINK, SLEEP
VM DEPLOY, EXECUTE, DEPOSIT, WITHDRAW
Staking STAKE, UNSTAKE, DELEGATE, COLLECT

Keepalive and Timeouts

Behavior Value
Server ping interval 30 seconds (WebSocket-level, automatic)
Missed pong Connection terminated immediately
Client with zero subscriptions Disconnected after 5 minutes of inactivity
Client with active subscriptions No timeout — stays alive indefinitely
Recommended client ping Every 25 seconds (application-level {"action":"ping"})

Reconnection and Catch-Up

  1. Track latest_action_index from WELCOME and every event’s action_index
  2. On disconnect, reconnect with exponential backoff
  3. Resubscribe with since_action_index set to your last known value
  4. Process events with catch_up: true (these are replayed, not live)
  5. Wait for CATCH_UP_COMPLETE before treating events as live
  6. If CATCH_UP_TOO_OLD error, use the REST API to backfill

Configuration

See CONFIGURATION.md for the WS_* environment variables that control the WebSocket server.

Variable Default Description
WS_ENABLED true Enable/disable WebSocket server
WS_POLL_INTERVAL 5000 Change detection poll interval (ms)
WS_PING_INTERVAL 30000 Server ping interval (ms)
WS_IDLE_TIMEOUT 300000 Idle timeout for zero-subscription clients (ms)
WS_MAX_CONNECTIONS_PER_IP 5 Max concurrent connections per IP
WS_MAX_SUBSCRIPTIONS 25 Max subscriptions per connection
WS_MAX_BACKPRESSURE 65536 Max buffered bytes before skipping a client

Edit this page on GitHub ↗