Okay, first off - go to helius.dev, sign up for a free account, grab your API, and hit the Webhooks tab. Boom. Add a new one, pick mainnet, slap in a dummy URL like their built in logger, add Tensor's swap program address (it's TsrB2hGfpa4vn7k64wC8V9E1eTq8wVCsP99EiUq7vB), set it to NFT sales. Create it. Now watch live Tensor NFT sales ping in real time. Why? 'Cause Helius parses hundreds of tx types out of the box. No server needed yet. Pretty much instant gratification.
In my experience, this tricks you into believing it's that easy. And it is. But stick around - we'll build the real deal.
Look, Solana's fast. Like, 50k TPS fast. Polling RPCs? You'll drown in rate limits and lag. Webhooks? Helius shoves events straight to your endpoint the second they happen. NFT sale? Token transfer? Wallet move? Ping. Fee's tiny too - think ~0.000005 SOL per tx in the payload.
The thing is, raw Solana txns are a mess. Base64 blobs, inner instructions everywhere. Helius "enhances" 'em - parses out the good stuff like feePayer, signature, tokenAmounts with decimals. Sound familiar if you've decoded a few yourself?
Honestly, I usually start here for any live app. Bots, alerts, dashboards. Scales to 100k addresses per hook. Free tier's generous too.
What's next? Test fire. Helius has a Send Test button. Fires fake data. Your endpoint must respond 200 OK quick, or retries fail after 3 tries. Miss that? Events drop. Brutal but fair.
Dashboard caps at 25 addresses. Need more? Curl it.
curl -X POST 'https://api mainnet.helius rpc.com/v0/webhooks' \
-H 'Content Type: application/json' \
-d '{ "accountAddresses": ["YourWalletHere"], "transactionTypes": ["ANY"], "webhookURL": "https://your server.com/webhook", "authHeader": {"": "shh secret"}, "webhookType": "enhanced"
}' \
-H 'Authorization: Bearer YOURAPIKEY'
Swap in your deets. Fire. Webhook ID comes back. Save it - you'll update later.
Now, you need somewhere to land these POSTs. I usually spin up Node/Express 'cause it's braindead easy. Or Cloudflare Workers if you're serverless and cheap.
Here's Express. Install nothing fancy - just express and body parser.
const express = require('express');
const app = express();
app.use(express.json()); app.post('/webhook', (req, res) => { console.log('Boom, event:', req.body); req.body.forEach(event => { if (event.type === 'NFT_SALE') { console.log(Sold ${event.tokenTransfers.tokenAmount.asDecimal} for ${event.nativeTransfers.tokenAmount.asDecimal} SOL); } }); res.status(200).send('OK'); // Critical!
}); app.listen(3000, () => console.log('Listening on 3000'));
Deploy to Render, Vercel, whatever. Ngrok it for local testing: ngrok http 3000. Grab the https URL. Plug into Helius. Test. Watch console explode with live txns.
Potential gotcha: Timeouts. Helius waits 10s max. Keep it snappy - no heavy DB writes in the handler. Offload to queues.
Tensor's program: TsrB2hGfpa4vn7k64wC8V9E1eTq8wVCsP99EiUq7vB. Set webhook to NFT_SALE + that address. Payload looks like this (trimmed):
{ "signature": "5xTxTx..", "type": "NFT_SALE", "source": "TENSOR", "tokenTransfers": [ { "tokenAmount": {"decimals": 0, "tokenAmount": "1"}, "tokenAccount": "25DTUAd1roBFoUQaxJQByL6Qy2cKQCBp4bK9sgfy9UiM", "userAccount": "1BWutmTvYPwDtmw9abTkS4Ssr8no61spGAvW1X6NDix" } ], "nativeTransfers": [{"tokenAmount": {"asDecimal": "420.69"}}], "fee": 5000, "slot": 1234567, "timestamp": 1234567890000
}
Why does this matter? Parse nativeTransfers for SOL price. tokenTransfers for NFT deets. Build a Discord bot? Slack alert? Easy.
In my experience, start with enhanced type. Raw's cheaper but you decode yourself. Enhanced? Parsed gold.
Okay, Node script time. Install @helius labs/helius sdk.
const { Helius } = require('helius sdk');
const fs = require('fs'); const helius = new Helius('YOURAPIKEY');
const creator = 'CREATORADDRESSHERE'; // e.g. Mad Lads dude async function snapshot() { const nfts = await helius.rpc.getAssetsByCreator({ creatorAddress: creator, page: 1 }); const addresses = nfts.items.map(nft => nft.id); fs.writeFileSync('addresses.json', JSON.stringify(addresses)); console.log(Snapped ${addresses.length} mints);
}
snapshot();
Run it. Spits addresses.json. Now update webhook:
const webhookId = 'YOURWEBHOOKID';
const addresses = JSON.parse(fs.readFileSync('addresses.json')); await helius.rpc.updateWebhook(webhookId, { accountAddresses: addresses, transactionTypes: ['NFTSALE', 'NFTBID']
});
Now every sale/bid in that collection pings you. Test with Helius' test button - it'll simulate.
Want free hosting? Workers. Acts as middleman: Helius → Worker → Telegram.
export default { async fetch(request, env) { if (request.method === 'POST') { const events = await request.json(); for (const event of events) { if (event.type === 'TRANSFER') { await fetch(https://api.telegram.org/bot${BOTTOKEN}/sendMessage, { method: 'POST', headers: { 'Content Type': 'application/json' }, body: JSON.stringify({ chatid: CHAT_ID, text: Transfer! ${event.tokenTransfers.userAccount} → ${event.tokenTransfers.userAccount} ${event.tokenTransfers.tokenAmount.asDecimal} }) }); } } } return new Response('OK', { status: 200 }); }
};
Env vars: BOTTOKEN, CHATHID. Deploy. Copy URL. Set as webhook target in Helius. Watch Telegram light up on real txns.
Issue? Workers cold start ms. Fine for Solana speed. But add authHeader in Helius for security: {"": "your secret"}. Check it in worker.
| What It Means | Example | |
|---|---|---|
| signature | Tx ID. Track it. | 5xTxTx..abc |
| type | Event kind. | NFT_SALE |
| fee | Tx fee in lamports (1 SOL = 1e9). | 5000 (~0.000005 SOL) |
| timestamp | Unix ms. Convert for humans. | 1673445241000 |
| tokenTransfers | Array of moves. Has asDecimal in enhanced. | -1 NFT from seller |
| nativeTransfers | SOL moves. | 420.69 SOL payment |
| accountData | Changed accounts. | Array of deltas |
Pro tip: Always check err: null. Failed txns still webhook if subscribed.
Hitting walls? Common ones.
Discord's gold: discord.gg/helius. Ask there. They're quick.
Webhooks fire on events. Need everything streaming? WS.
const ws = new WebSocket('wss://mainnet.helius rpc.com/?api=YOUR_KEY');
ws.on('open', () => { ws.send(JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'subscribe', params: [{ account: ['YourWallet'] }] }));
});
ws.on('message', data => console.log(JSON.parse(data)));
But for most? Webhooks. Push, not pull.
Say you're watching your own bag. Add your wallet address. Set types: SWAP, TRANSFER. Endpoint emails ya? Texts? Boom, notified instantly. Better than Phantom alerts.
I usually filter payload server side. Like, ignore dust: if tokenAmount.asDecimal < 0.01, skip. Clean noise.
One more: Update webhooks anytime. Same endpoint: PATCH /v0/webhooks/{id}. Add/remove addresses dynamically. Power move for dashboards.