How to Track Live Stellar Transactions in Real Time
Stellar processes 5-10 million transactions per day. If you are building a payment app, monitoring tool, or analytics dashboard, you need to see these transactions as they happen. This guide covers three approaches: cursor-based polling, Server-Sent Events (SSE), and the LumenQuery live transaction viewer.
Approach 1: Cursor-Based Polling
The simplest method — poll Horizon with a cursor to get only new transactions:
const HORIZON = 'https://horizon.stellar.org';
async function streamTransactions(onTransaction) {
// Get starting cursor
const initial = await fetch(`${HORIZON}/transactions?limit=1&order=desc`);
const data = await initial.json();
let cursor = data._embedded.records[0].paging_token;
// Poll every 5 seconds
setInterval(async () => {
try {
const res = await fetch(
`${HORIZON}/transactions?cursor=${cursor}&order=asc&limit=100`
);
const batch = await res.json();
for (const tx of batch._embedded.records) {
onTransaction(tx);
cursor = tx.paging_token;
}
} catch (err) {
console.error('Poll error:', err.message);
}
}, 5000);
}
streamTransactions((tx) => {
console.log(`TX ${tx.hash.slice(0, 8)}... | ${tx.operation_count} ops | fee: ${tx.fee_charged}`);
});Pros and Cons
Approach 2: Horizon SSE Streaming
Horizon supports Server-Sent Events for real-time streaming:
const EventSource = require('eventsource');
const es = new EventSource(
`${HORIZON}/transactions?cursor=now&order=asc`
);
es.onmessage = (event) => {
const tx = JSON.parse(event.data);
console.log(`TX ${tx.hash.slice(0, 8)}... | ledger ${tx.ledger}`);
};
es.onerror = (err) => {
console.error('SSE error, reconnecting...');
};Filtering by Account
Stream only transactions for a specific account:
const es = new EventSource(
`${HORIZON}/accounts/${ACCOUNT_ID}/transactions?cursor=now`
);Filtering by Payments
Stream only payment operations:
const es = new EventSource(
`${HORIZON}/payments?cursor=now`
);Approach 3: Decoding Operations
Raw transactions are not very readable. Decode the operations for human-friendly output:
async function decodeTransaction(tx) {
const opsRes = await fetch(`${HORIZON}/transactions/${tx.hash}/operations`);
const ops = (await opsRes.json())._embedded.records;
return ops.map(op => {
switch (op.type) {
case 'payment':
return `Payment: ${op.amount} ${op.asset_type === 'native' ? 'XLM' : op.asset_code} from ${op.from.slice(0,8)}... to ${op.to.slice(0,8)}...`;
case 'create_account':
return `Create account ${op.account.slice(0,8)}... with ${op.starting_balance} XLM`;
case 'change_trust':
return `Trust ${op.asset_code}:${op.asset_issuer?.slice(0,8)}...`;
case 'invoke_host_function':
return `Soroban contract invocation`;
default:
return `${op.type}`;
}
});
}LumenQuery Live Transaction Viewer
If you do not want to build your own, LumenQuery provides a pre-built live transaction viewer at /dashboard/transactions:
The viewer uses SSE under the hood and decodes all operation types including Soroban contract calls.
Rate Limit Considerations
When streaming transactions, be aware of rate limits:
For production streaming, use a managed API endpoint with higher limits.
*Stream live Stellar transactions with LumenQuery. Our live viewer decodes operations in real time — or use the API to build your own.*