How to Build a Stellar Payment Dashboard Using Horizon API Data
A payment dashboard is one of the most practical things you can build on Stellar. It shows account balances, tracks incoming and outgoing payments, displays transaction status, and gives users visibility into their on-chain activity. This tutorial walks through building one using the Horizon API.
What We Are Building
A dashboard that displays:
Fetching Account Balances
The /accounts/{id} endpoint returns everything about an account in a single call:
const HORIZON = 'https://horizon.stellar.org';
async function getAccountData(accountId) {
const res = await fetch(`${HORIZON}/accounts/${accountId}`);
if (res.status === 404) return { error: 'Account not found or not funded' };
const account = await res.json();
return {
id: account.account_id,
sequence: account.sequence,
balances: account.balances.map(b => ({
asset: b.asset_type === 'native' ? 'XLM' : `${b.asset_code}`,
balance: parseFloat(b.balance),
issuer: b.asset_issuer || null,
})),
signers: account.signers.length,
subentries: account.subentry_count,
homeDomain: account.home_domain || null,
};
}Displaying Balances
function renderBalances(balances) {
return balances
.sort((a, b) => b.balance - a.balance)
.map(b => `${b.asset}: ${b.balance.toLocaleString()}`)
.join('\n');
}Fetching Payment History
The /accounts/{id}/payments endpoint returns all payment-type operations:
async function getPayments(accountId, limit = 20) {
const res = await fetch(
`${HORIZON}/accounts/${accountId}/payments?limit=${limit}&order=desc`
);
const data = await res.json();
return data._embedded.records.map(p => ({
id: p.id,
type: p.type,
createdAt: p.created_at,
amount: p.amount,
asset: p.asset_type === 'native' ? 'XLM' : p.asset_code,
from: p.from,
to: p.to,
direction: p.to === accountId ? 'incoming' : 'outgoing',
transactionHash: p.transaction_hash,
}));
}This gives you the data to render a payment feed with direction indicators, amounts, and timestamps.
Fetching Transaction Details
For each payment, you can fetch the full transaction to show status and fees:
async function getTransaction(hash) {
const res = await fetch(`${HORIZON}/transactions/${hash}`);
const tx = await res.json();
return {
hash: tx.hash,
ledger: tx.ledger,
successful: tx.successful,
feeCharged: parseInt(tx.fee_charged),
operationCount: tx.operation_count,
memo: tx.memo || null,
memoType: tx.memo_type,
createdAt: tx.created_at,
};
}Fetching Current Ledger Info
Show the user what is happening on the network right now:
async function getNetworkStatus() {
const [ledgerRes, feeRes] = await Promise.all([
fetch(`${HORIZON}/ledgers?limit=1&order=desc`),
fetch(`${HORIZON}/fee_stats`),
]);
const ledger = (await ledgerRes.json())._embedded.records[0];
const fees = await feeRes.json();
return {
currentLedger: ledger.sequence,
closedAt: ledger.closed_at,
transactionCount: ledger.successful_transaction_count,
baseFee: parseInt(fees.fee_charged.min),
protocolVersion: ledger.protocol_version,
};
}Putting It Together
async function buildDashboard(accountId) {
const [account, payments, network] = await Promise.all([
getAccountData(accountId),
getPayments(accountId, 10),
getNetworkStatus(),
]);
console.log('=== Account ===');
console.log(`ID: ${account.id}`);
account.balances.forEach(b =>
console.log(` ${b.asset}: ${b.balance.toLocaleString()}`)
);
console.log('\n=== Recent Payments ===');
payments.forEach(p =>
console.log(` ${p.direction === 'incoming' ? '←' : '→'} ${p.amount} ${p.asset} (${p.createdAt})`)
);
console.log('\n=== Network ===');
console.log(` Ledger: #${network.currentLedger}`);
console.log(` TXs in last ledger: ${network.transactionCount}`);
console.log(` Base fee: ${network.baseFee} stroops`);
}Auto-Refreshing
Poll for new payments using cursors:
async function pollPayments(accountId, onNewPayment) {
let cursor = 'now';
setInterval(async () => {
const res = await fetch(
`${HORIZON}/accounts/${accountId}/payments?cursor=${cursor}&order=asc&limit=50`
);
const data = await res.json();
for (const payment of data._embedded.records) {
onNewPayment(payment);
cursor = payment.paging_token;
}
}, 5000);
}Next Steps
*Build your payment dashboard on reliable infrastructure. LumenQuery provides managed Horizon API with sub-100ms response times. Start free.*