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
- Track
latest_action_indexfromWELCOMEand every event’saction_index - On disconnect, reconnect with exponential backoff
- Resubscribe with
since_action_indexset to your last known value - Process events with
catch_up: true(these are replayed, not live) - Wait for
CATCH_UP_COMPLETEbefore treating events as live - If
CATCH_UP_TOO_OLDerror, 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 |