Solutions · Telegram bots
MadeOnSol sends developers who build Telegram bots signed webhook events for wallet activity, KOL trades, deployer launches and token surges, which their backend verifies and turns into a message with the Telegram Bot API.
Telegram is the interface your users see; the watching, parsing and delivery behind it run on MadeOnSol. This page is for developers building a bot, not a bot to install.
Webhook alerts from Pro. A Free key reads the KOL feed and deployer alerts over REST, 5 minutes delayed, with no webhooks or streams. A bot other people use needs Business.
What your users see
Your alert bot
bot
whale #1 buy 2.5 SOL
8vdc...pump
wallet_tracker:eventyour handlersendMessageFor developers building a bot. Want a Telegram trading bot to use instead? Compare Telegram bots
The problem
An alert bot looks like a chat window, but most of the work happens before a message exists: watching addresses and launchpads around the clock, turning transactions into readable events, knowing which wallet or deployer matters, and delivering each event once when servers restart. Put all of that inside the bot process and you are running an indexer with a chat front end.
Show me
Four pieces: one registration, the signed POST you receive, a handler that verifies and deduplicates, and a plain Bot API call. Header names, payload keys and the signature scheme are the code's; values are illustrative.
Pick the events and a delivery filter. The response returns the webhook's secret once; store it with your bot token.
POST /api/v1/webhooks
Authorization: Bearer msk_...
Content-Type: application/json
{
"url": "https://bot.example.com/madeonsol",
"events": ["wallet_tracker:event", "deployer:alert"],
"filters": { "deployer_tier": ["elite"] }
}Each event arrives as one HTTPS POST: an envelope of event, data and timestamp, signed in the headers.
POST /madeonsol
Content-Type: application/json
X-MadeOnSol-Timestamp: 1790157600421
X-MadeOnSol-Signature: 3b9f...c7a2
X-MadeOnSol-Event: wallet_tracker:event
User-Agent: MadeOnSol-Webhook/1.0
{
"event": "wallet_tracker:event",
"data": {
"subscriber": "<your user id>",
"wallet_address": "7xKX...3bPq",
"label": "whale #1",
"event_type": "swap",
"action": "buy",
"token_mint": "8vdc...pump",
"sol_amount": 2.5,
"token_amount": 1000000,
"counterparty": null,
"tx_signature": "5Hyj...kMnP",
"block_time": 1790157600,
"slot": 449476673,
"replayed": false,
"ts": "2026-09-23T10:00:00.000Z"
},
"timestamp": "2026-09-23T10:00:00.418Z"
}Check the HMAC over the raw body, answer fast, then drop anything you have already sent.
import crypto from "node:crypto";
import express from "express";
const app = express();
const seen = new Set<string>(); // use Redis or a unique DB column in production
app.post("/madeonsol", express.raw({ type: "application/json" }), (req, res) => {
const ts = req.get("X-MadeOnSol-Timestamp") ?? "";
const sig = req.get("X-MadeOnSol-Signature") ?? "";
const expected = crypto
.createHmac("sha256", process.env.MADEONSOL_WEBHOOK_SECRET!)
.update(`${ts}.${req.body}`) // the raw body, never re-serialized JSON
.digest("hex");
const fresh = Math.abs(Date.now() - Number(ts)) < 5 * 60_000;
if (!fresh || sig.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return res.sendStatus(401);
}
res.sendStatus(200); // answer inside the 10 s delivery timeout, then work
const { event, data } = JSON.parse(req.body.toString("utf8"));
// The envelope has no event id: key on the payload's own identity.
const key = event === "deployer:alert"
? `deployer:alert:${data.alert_id}`
: `${event}:${data.tx_signature}:${data.wallet_address}`;
if (seen.has(key)) return;
seen.add(key);
sendAlert(event, data).catch(console.error);
});Format the fields you care about and call sendMessage. This is the whole Telegram side.
// Token symbols and labels are free text: escape them for parse_mode HTML.
const esc = (s: unknown) =>
String(s ?? "").replace(/[<>&]/g, (c) => ({ "<": "<", ">": ">", "&": "&" })[c]!);
async function sendAlert(event: string, d: Record<string, any>) {
const text = event === "deployer:alert"
? `<b>${esc(d.deployer_tier)} deployer</b> launched <b>${esc(d.token_symbol)}</b>\n<code>${esc(d.token_mint)}</code>`
: `<b>${esc(d.label ?? d.wallet_address)}</b> ${esc(d.action ?? d.event_type)} ${esc(d.sol_amount)} SOL` +
(d.replayed ? " (recovered)" : "") +
`\n<code>${esc(d.token_mint ?? d.tx_signature)}</code>`;
// Standard Telegram Bot API from here on: nothing MadeOnSol-specific.
const r = await fetch(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ chat_id: process.env.TELEGRAM_CHAT_ID, text, parse_mode: "HTML" }),
});
if (!r.ok) throw new Error(`Telegram ${r.status}`); // on 429, wait retry_after and resend
}Signature
Hex HMAC-SHA256 of the timestamp header, a dot and the raw body, keyed with your webhook secret. Every retry is signed again with a fresh timestamp, so a 5-minute freshness check never rejects a real retry.
No event id
The envelope carries event, data and timestamp only. A retry resends the same body, so deduplicate on alert_id for deployer alerts and on tx_signature plus wallet_address for wallet and KOL events.
Filters
deployer_tier narrows deployer alerts. Webhook filters do not narrow wallet_tracker:event: every event from your own watchlist is delivered, so apply size rules in your handler.
Try it
A Free key answers REST lookups right away, enough for a bot that checks a mint on command. Push alerts start on Pro: once a webhook is registered, POST /api/v1/webhooks/test sends a signed sample marked _test: true, so your handler is checked before a real event arrives.
Architecture rule
The bot process formats and sends. Everything below belongs to the layer behind it, and each rule comes from how delivery actually behaves.
Avoid: Decode transactions or hold an RPC subscription
Instead: Receive parsed events. A wallet_tracker:event already says swap or transfer, buy or sell, which token and how much SOL.
event_typeactiontoken_mintsol_amountAvoid: Poll the REST feeds for new alerts
Instead: Let webhooks push each event. On a Free key the KOL feed and deployer alerts are 5 minutes delayed, so a polling alert bot on Free is late by design.
Avoid: Call Telegram inside the webhook request
Instead: Answer 2xx first, then send. MadeOnSol waits 10 seconds per attempt, retries 5xx and 429, does not retry other 4xx, and pauses a webhook after 10 failed deliveries in a row.
Avoid: Add the same wallet once per chat
Instead: Watch each address once and keep the chat-to-wallet mapping in your own database. Every event carries your label; watchlist size depends on your plan.
labelwallet_addressAvoid: Post a recovered event as if it just happened
Instead: Check replayed. It is true when the tracker recovered the event after a reconnect; mark it in the message or skip it.
replayedslotAvoid: Treat webhook delivery as your only record
Instead: Nothing is queued while your endpoint or a paused webhook is down. After downtime, backfill from REST, for example /api/v1/wallet-tracker/trades.
Use cases
The same event reaches you as a webhook or on the WebSocket stream; the plan column shows the first plan for each. Watchlist setup in the Wallet Tracker API
| Alert | Webhook event | WebSocket channel | Plan |
|---|---|---|---|
| Wallet activitySwaps and SOL transfers of the addresses on your watchlist. | wallet_tracker:event | wallet_tracker:events | Webhook: from ProStream: from Ultra |
| KOL tradesBuys and sells by tracked KOL wallets, with market cap at the trade. | kol:trade | kol:trades | Webhook: from ProStream: from Pro |
| Launches by tracked deployersNew tokens from elite, good or rising deployers, with bonding rate and the dev buy. | deployer:alert | deployer:alerts | Webhook: from ProStream: from Pro |
| GraduationsA tracked deployer's token completes its bonding curve, with time to bond. | deployer:bond | deployer:alerts | Webhook: from ProStream: from Pro |
| Token surges and revivalsYoung tokens running against their launch market cap, and dormant tokens trading again, with risk flags. | token:surgetoken:revival | token:surges | Webhook: from ProStream: from Pro |
| New deploys for scanner botsNew tokens with the deployer's tier. Pro receives elite and good deployers only; Ultra receives all. | sniper:deploy | sniper:deploys | Webhook: from ProStream: from Pro |
| Copy-trade signalsYour copy-trade rule on tracked KOL wallets fires. Sent to the rule's own webhook_url with its own secret, same headers and signature. | copytrade:signalrule's own webhook_url | copytrade:signals | Webhook: from ProStream: from Pro |
Plans
The transport sets the minimum plan; who reads the bot sets the licence. See what each plan includes
Architecture
Your bot keeps the Telegram token and the chat mapping; MadeOnSol keeps the watching and the parsing.
1 · Source
Chain activity
Transactions and program events
2 · MadeOnSol
Data and intelligence layer
3 · Delivery
4 · Yours
Your webhook handler and send queue
Keys and business logic stay server-side
5 · Users
Your Telegram bot and chats
Build outcomes
A bot that posts MadeOnSol data into other people's chats displays it to your users, which requires a Business plan. See pricing
Keep going
Solution
Wallet Tracking
The watchlist behind wallet alerts.
Solution
Copy Trading
KOL copy-trade signals, sent to the rule's own webhook.
Product
Token surge alerts
The surge and revival fires, in the browser.
Product
Deployer Hunter
Track records behind each launch alert.
Docs
Streaming reference
Channels and frames for a socket-based bot.
Guide
Data layer for a Telegram bot SaaS
Features, endpoints and architecture.
Guide
Verify signed webhooks in Node.js and Python
The signature check, in depth.
Guide
The Telegram side from scratch with grammY
Bot setup, commands and structure.
FAQ
Next step
Check that the API is up, prototype the command side on a free key, then register the webhook that feeds your alerts.