Solana Event Listeners Guide: Real Time Setup (2026)

Okay, so most people jumping into Solana event listeners? They fire up onLogs without a solid RPC connection. Boom. Misses every event. Why? Public endpoints like api.mainnet beta.solana.com throttle you hard after a few seconds. Your callback never even hits.

The right way? Grab a dedicated WebSocket endpoint from the jump. I use QuickNode or Helius - they handle the heavy lifting. Costs like 0.000005 SOL per log pull equivalent, but honestly, free tiers get you far for testing. Set it up first, test with a simple ping. Sound familiar? You've been there.

Quick Project Setup - Don't Skip This

  • npm init -y. Duh.
  • npm install @solana/web3.js
  • Touch listen events.js
  • node listen events.js - watch magic.

That's it. No fluff. In my experience, skipping the init screws package.json later. Annoying.

Pick Your Network Smart

Mainnet? Use "https://api.mainnet beta.solana.com" for HTTP, but flip to wss:// for real time. Devnet's kinder: "https://api.devnet.solana.com". Fees? Negligible, like ~0.000005 SOL per subscription.

Core Connection Code - Copy This

Look, here's the skeleton. Replace YOURPROGRAMID with whatever program's logs you're chasing. Like a DEX or NFT drop contract.

import { Connection, PublicKey } from "@solana/web3.js"; const PROGRAMID = new PublicKey("YOURPROGRAMIDHERE");
const URL = "wss://api.mainnet beta.solana.com"; // WebSocket for real time
const connection = new Connection(URL, "confirmed"); console.log("Connected. Listening..");

Run it. See "Connected"? Good. Now the fun part.

Hooking Into Logs - The Money Maker

So onLogs is your bread and butter. It streams every log from that program ID. Transactions, swaps, whatever. Callback fires instantly.

But here's the deal - logs are raw strings. "Instruction: Swap". You parse 'em. I usually slap in a filter right away.

connection.onLogs(PROGRAM_ID, async (logInfo) => { const logs = logInfo.logs; for (const log of logs) { if (log.includes("Instruction: Swap")) { // Your event name console.log("Swap detected!", log); // Process here } }
}, "confirmed");

Why "confirmed"? Faster than "finalized", but safe enough. "Processed" is quickest, risks dupes though.

Real Talk: Parsing Those Logs

Logs ain't JSON. Messy. Build an extract function. Say you're watching token swaps - grab amounts, addresses.

function extractSwapData(logs) { // Hunt for patterns like "Amount: 1000 USDC" const amountMatch = logs.find(l => l.includes("Amount:")); return { amount: amountMatch ? amountMatch.split(":").trim() : "unknown", timestamp: new Date(), raw: logs };
}

Call it in your callback. console.log(extractSwapData(logs)). Boom, structured data. In my experience, regex gets fancy here, but start simple.

Account Changes? Even Easier

  1. Pick a wallet: const ACCOUNT = new PublicKey("wallet you want");
  2. connection.onAccountChange(ACCOUNT, (info) => { console.log("Balance:", info.lamports / 1e9, "SOL"); }, "confirmed");
  3. Grab subscriptionId it spits back. Save it.

Test it. Request an airdrop on devnet: connection.requestAirdrop(ACCOUNT, 1e9). Wait 10 secs. Watch balance jump. That's your listener working live.

What's next? Unsubscribe when done. connection.removeAccountChangeListener(subscriptionId). Clean.

Compare Listeners: Quick Table

ListenerWhat It DoesBest ForGotchas
onLogsProgram logsCustom events, DEX tradesParse manually
onAccountChangeWallet balance shiftsAlerts, botsOnly account data
onProgramAccountChangeProgram owned accountsNFT metadata, positionsFilter by data
signatureSubscribeTx confirmationsTrack sendsOne off

Pick based on need. onLogs covers 80% of cases. Pretty much.

Scaling Up - Don't Crash on Volume

Solana blasts blocks every 400ms. Thousands of txs. Your basic listener? Fine for dev. Production? It'll choke.

First fix: WebSockets only. HTTP polling sucks - misses stuff, lags. Use wss:// from QuickNode. Free dev tier handles events/sec.

But websockets flake. Reconnect logic, man.

let subId;
async function subscribeWithRetry() { try { subId = await connection.onLogs(..); } catch (e) { console.log("RPC hiccup. Retry in 5s.."); setTimeout(subscribeWithRetry, 5000); }
}
subscribeWithRetry();

I usually wrap in try catch. Logs errors to file. Saved my bot once during congestion.

Heavy traffic? Ditch raw RPC. Jump to webhooks. Helius does this killer - POST events to your endpoint. No parsing needed. They even decode instructions. Free for 1000 events/day. Scales to 100k addresses per hook. Game changer.

Webhook Quick Win

  1. Sign up Helius. Grab API.
  2. POST to their dashboard: program ID, your server URL.
  3. Your /webhook endpoint: req.body has parsed event. JSON ready.
  4. Slack it, DB it, trade on it.

No code on chain side. Latency? Sub-100ms. Beats websockets for reliability.

Geyser - If You're Serious (2026 Pro Tip)

Okay, 2026 now. Geyser's matured. Validators stream direct to your Kafka or DB. Zero RPC middleman. Latency? 5ms. For HFT, DeFi liqs.

Don't self host unless masochist. Chainstack or Helius Geyser as a Service. ~$50/mo starter. Streams Raydium swaps, Bonk pumps live.

Setup? Docker their plugin. Point to your endpoint. Events flood in: txs, accounts, slots. Filter server side.

Why does this matter? Public RPCs cap at 100 reqs/sec. Geyser? Unlimited. Built for Solana's speed.

Bots and Real Apps - Put It Together

Say you're building a swap sniper. Listen Raydium program logs for new pools.

Step 1: onLogs for "initialize pool" instruction.

Step 2: Extract token addresses from log.

Step 3: Check liquidity > 10k USDC.

Step 4: Auto buy if good odds.

Fees kill ya? Bundle txs. ~0.000005 SOL each. Gas wars? Use Jito for priority.

In my experience, add rate limits. Solana's fast - your bot spams, you ban. Sleep 50ms between checks.

Potential issue: Dupes. Same tx hits multiple validators. Track signatures. Set has unique tx sigs.

Alerts and Notifications - Easy Wins

Discord bot? onAccountChange your wallet. Balance drops >1 SOL? Ping channel.

if (oldBalance - newBalance > 1e9) { // Discord webhook POST
}

Or Dialect for pushies. Mobile alerts on big transfers.

NFT drops? ProgramSubscribe to minter. "Instruction: Mint". Grab metadata, alert collectors.

Troubleshooting - When Shit Hits

No events? Check PROGRAM_ID. Wrong? Nada.

Connection drops? Logs "WebSocket error". Retry loop fixes.

Missing txs? Commitment level. "confirmed" over "processed".

Parse fails? Logs format changed. Programs update. Test on devnet first.

High fees? Nah, Solana's cheap. But spam? Prioritize with Jito bundles. 0.01 SOL tip max.

One more: Testnet airdrops only 24 SOL/day. Use multiple wallets.

Prod Checklist

  • WebSocket + retry
  • Signature dedupe
  • DB persistence (Postgres cheap)
  • Monitoring (Grafana on events/sec)
  • Alerts on downtime

Advanced: Custom Events with Anchor

Building your program? Use Anchor. Easiest events.

#[event]
pub struct MyEvent { pub value: u64,
} program.addEventListener("MyEvent", (event, slot) => { console.log(event.value);
});

Typed. Auto parsed. No regex hell. Remove listener when done.

Can't scan history tho. Events live only during tx. Want past? getSignaturesForAddress + getTransaction. Slow for volume.

Honestly? For history, ETL to your DB via Geyser. Query anytime.

Cost Breakdown - No BS

MethodCostLatencyReliability
Public RPCFree500ms+Low
QuickNode WS$9/mo50msGood
Helius WebhooksFree-$$100msHigh
Geyser Service$50+/mo5msGod tier

Start free. Scale as needed. I went QuickNode → Helius. Never looked back.

Last tip: Run in PM2. node --cluster. Handles crashes. Logs to file.