Quick start
Sign in to the dashboard
Open the web dashboard with the Telegram account the network is registered to. “My bots” shows the card of your network.
Get the key
In the network card open “Integration” and press “New key”. The key is shown once — keep it on your server and never publish it. If it leaks, issue a new one in the same place: the old key stops working at once.
Put the code into your bots
Take the ready code below. It is the same for every bot of the network: the bot name is filled in automatically.
Call it in two places
On /start — a welcome impression; after the bot has delivered its result to the person — a regular one. See “When to call”.
Check
Go through the five-point check on one bot, then roll out to the rest.
There is no need to register bots in advance. A bot joins the network at its first request and gets ads right away: bots are not moderated one by one.
Request
POST https://hiviews.net/ with a JSON body.
Headers
| Header | Value |
|---|---|
Authorization | The network key as is, without a Bearer prefix |
Content-Type | application/json |
Body fields
| Field | Type | Description |
|---|---|---|
UserId | int, required | Telegram id of the user the bot will show the ad to. Private chats only. |
Bot | string, required | Username of the bot the ad is requested for, without @ (case does not matter). It must be a real bot username: Latin letters, digits and underscores, ending in bot. |
StartPlace | bool | true — a welcome impression (on /start); false — a regular impression after the result. Without the field the request counts as a regular impression. |
UserFirstName | string | The user's first name: replaces $name in the post text. |
Username | string | The user's username without @, if any. |
Language | string | The user's Telegram language (language_code). Campaigns are matched by it. If it is missing we try to work the language out ourselves (from earlier ads to this person and from the name), but sending it is more accurate. |
HasPhoto | boolean | Optional. true if the user has a profile photo (getUserProfilePhotos → total_count > 0; cache the result). It matters for campaigns marked “Verified”: only people with a photo and an ordinary name get them. If you do not send it, we check those who have a username ourselves — so always send Username. Do not send true for everyone: such bots stand out by their clicks and lose paid impressions. |
Example
curl -X POST https://hiviews.net/api/partner/ad \
-H "Authorization: YOUR_NETWORK_KEY" \
-H "Content-Type: application/json" \
-d '{"UserId": 123456789, "UserFirstName": "Ivan", "Language": "en", "StartPlace": true, "Bot": "my_first_bot"}'Response
The response is always JSON. With an ad — "ok": true and an ad object; without one — "ok": false and a reason (all values).
{
"ok": true,
"ad": {
"text": "<a href=\"https://hiviews.net/api/go/v/…\">​</a>Ivan, …",
"parse_mode": "HTML",
"reply_markup": {"inline_keyboard": [[{"text": "Open", "url": "https://hiviews.net/api/go/…"}]]},
"photo": "https://hiviews.net/api/go/m/…",
"delete_after": 60
}
}ad field | What to do with it |
|---|---|
text | The post text in HTML markup. Send it unchanged. With media, it is the caption. |
parse_mode | Always HTML — pass it to the send method. |
reply_markup | A keyboard with link buttons. Pass it as is. |
photo / video / animation | At most one is present: a link to the file. Send it with sendPhoto, sendVideo or sendAnimation, passing the link as the file. Without it — sendMessage. |
delete_after | If present, delete the message after that many seconds. This marks external ads, which are shown briefly. |
How to send the post so the impression is paid
We cannot see what a bot sends to a user, so Telegram itself confirms the impression. Every post starts with an invisible link: when the message is sent, Telegram's servers open it and the impression is counted.
- Send the post exactly as you received it. Do not cut the beginning of the text, rebuild the markup, or change links in the text or the buttons.
- Keep link previews on. Do not pass
disable_web_page_previeworlink_preview_optionsthat disable them. Many libraries switch previews off globally in the bot defaults — switch them on for the ad message. - Send right away. A confirmation is accepted within 3 minutes of handing out the post. A post sent later is not paid.
- One request — one message to one person. Do not send the post to other users, and do not request ads without a user action.
An unconfirmed post is not paid: neither the impression nor its clicks. If a bot takes posts and does not send more than half of them, it stops getting ads — the answer is unconfirmed_sends.
When to call
| Moment | StartPlace | Format |
|---|---|---|
| The person pressed /start | true | A welcome impression. Call before your own greeting or right after it. |
| The bot delivered its result: found a track, downloaded a file, answered a request | false | A regular impression. Call after the result has been sent, so the ad is the last message. |
- Call in both places — that is how bots earn on both formats.
- Do not send
StartPlace: trueeverywhere: a welcome impression goes to one person in one bot at most once a day, extra requests come back without an ad. - The request must not hold the bot up: use a 5-second timeout and catch errors. The ready code does.
Ready code
The code is the same for every bot of the network. The bot name for the Bot field is taken from the bot itself, so nothing changes when you launch a new bot.
Python, aiogram 3
# HiViews bot network: one key for all bots. The bot sends the ad itself.
# pip install aiogram aiohttp
import asyncio, aiohttp
from aiogram import Bot
from aiogram.types import InlineKeyboardMarkup
HIVIEWS_KEY = "YOUR_NETWORK_KEY"
async def show_ad(bot: Bot, user, start: bool = False) -> None:
"""Call with start=True on /start, and with start=False after your bot delivered its result."""
me = await bot.me() # the bot that is answering the user right now
payload = {"UserId": user.id, "UserFirstName": user.first_name, "Username": user.username,
"Language": user.language_code, "StartPlace": start, "Bot": me.username}
try:
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=5)) as s:
async with s.post("https://hiviews.net/api/partner/ad", json=payload,
headers={"Authorization": HIVIEWS_KEY}) as r:
ad = (await r.json()).get("ad")
if not ad:
return # no ad for this person right now
kw = {"parse_mode": "HTML", "reply_markup": InlineKeyboardMarkup.model_validate(ad["reply_markup"])}
if ad.get("photo"):
msg = await bot.send_photo(user.id, ad["photo"], caption=ad["text"], **kw)
elif ad.get("video"):
msg = await bot.send_video(user.id, ad["video"], caption=ad["text"], **kw)
elif ad.get("animation"):
msg = await bot.send_animation(user.id, ad["animation"], caption=ad["text"], **kw)
else:
msg = await bot.send_message(user.id, ad["text"], **kw) # do not disable link previews
if ad.get("delete_after"): # such a post is removed after a minute unless it was clicked
asyncio.get_running_loop().call_later(
ad["delete_after"], lambda: asyncio.ensure_future(_quiet_delete(bot, user.id, msg.message_id)))
except Exception:
pass # ads must never break the bot
async def _quiet_delete(bot: Bot, chat_id: int, message_id: int) -> None:
try:
await bot.delete_message(chat_id, message_id)
except Exception:
pass
# Example: await show_ad(bot, message.from_user, start=True) # /start
# await show_ad(bot, message.from_user) # after the result is deliveredNode.js, Telegraf
// HiViews bot network: one key for all bots. Node.js 18+, Telegraf.
const HIVIEWS_KEY = process.env.HIVIEWS_KEY; // YOUR_NETWORK_KEY
async function showAd(ctx, start = false) {
// Call showAd(ctx, true) on /start, and showAd(ctx, false) after your bot delivered its result.
try {
const u = ctx.from;
const res = await fetch("https://hiviews.net/api/partner/ad", {
method: "POST",
headers: { Authorization: HIVIEWS_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ UserId: u.id, UserFirstName: u.first_name, Username: u.username,
Language: u.language_code, StartPlace: start,
Bot: ctx.botInfo.username }), // the bot that is answering the user right now
signal: AbortSignal.timeout(5000),
});
const { ad } = await res.json();
if (!ad) return; // no ad for this person right now
const extra = { parse_mode: "HTML", reply_markup: ad.reply_markup };
let msg;
if (ad.photo) msg = await ctx.telegram.sendPhoto(u.id, ad.photo, { caption: ad.text, ...extra });
else if (ad.video) msg = await ctx.telegram.sendVideo(u.id, ad.video, { caption: ad.text, ...extra });
else if (ad.animation) msg = await ctx.telegram.sendAnimation(u.id, ad.animation, { caption: ad.text, ...extra });
else msg = await ctx.telegram.sendMessage(u.id, ad.text, extra); // do not disable link previews
if (ad.delete_after) { // such a post is removed after a minute unless it was clicked
setTimeout(() => ctx.telegram.deleteMessage(u.id, msg.message_id).catch(() => {}), ad.delete_after * 1000);
}
} catch (e) { /* ads must never break the bot */ }
}
// Example: bot.start(async (ctx) => { await showAd(ctx, true); /* ... */ });PHP
<?php
// HiViews bot network: one key for all bots.
const HIVIEWS_KEY = 'YOUR_NETWORK_KEY';
/** $user is the "from" array of the Telegram update, $botUsername is the bot's username without @. On /start: $start = true; after your result: false.
* Returns [message_id, delete_after] — if delete_after is not null, delete the message after that many seconds. */
function hiviews_show_ad(string $botToken, string $botUsername, array $user, bool $start = false): ?array
{
$ch = curl_init('https://hiviews.net/api/partner/ad');
curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5,
CURLOPT_HTTPHEADER => ['Authorization: ' . HIVIEWS_KEY, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'UserId' => $user['id'], 'UserFirstName' => $user['first_name'] ?? '',
'Username' => $user['username'] ?? null, 'Language' => $user['language_code'] ?? null,
'StartPlace' => $start, 'Bot' => $botUsername, // the bot that is answering the user right now
]),
]);
$ad = json_decode((string) curl_exec($ch), true)['ad'] ?? null;
curl_close($ch);
if (!$ad) {
return null; // no ad for this person right now
}
$params = ['chat_id' => $user['id'], 'parse_mode' => 'HTML', 'reply_markup' => json_encode($ad['reply_markup'])];
if (!empty($ad['photo'])) {
$method = 'sendPhoto'; $params += ['photo' => $ad['photo'], 'caption' => $ad['text']];
} elseif (!empty($ad['video'])) {
$method = 'sendVideo'; $params += ['video' => $ad['video'], 'caption' => $ad['text']];
} elseif (!empty($ad['animation'])) {
$method = 'sendAnimation'; $params += ['animation' => $ad['animation'], 'caption' => $ad['text']];
} else {
$method = 'sendMessage'; $params += ['text' => $ad['text']]; // do not disable link previews
}
$ch = curl_init('https://api.telegram.org/bot' . $botToken . '/' . $method);
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10,
CURLOPT_POSTFIELDS => $params]);
$sent = json_decode((string) curl_exec($ch), true);
curl_close($ch);
return [$sent['result']['message_id'] ?? null, $ad['delete_after'] ?? null];
}
// Example: hiviews_show_ad($botToken, 'my_first_bot', $update['message']['from'], true); // /startNo ad: the reason values
With "ok": false there is nothing to send — the bot simply carries on. The reason field tells you why.
| HTTP | reason | What it means and what to do |
|---|---|---|
| 200 | no_ads | No fitting ad for this person right now. Normal. |
| 200 | cap_viewer_window, cap_hello_window, cap_daily_cap, cap_external | This person saw an ad recently. The caps protect users: at most one ad an hour and six a day across the network, a welcome ad once a day in one bot. |
| 200 | placement_off | This format is switched off in the network settings in the dashboard. |
| 200 | parallel | Another request for the same person arrived at the same moment. Nothing to do. |
| 200 | rate_limited | Too many requests per minute (see limits). Requests above the limit just get no ad. |
| 200 | unconfirmed_sends | Telegram did not confirm more than half of this bot's recent posts. Check that the post is sent unchanged and with link previews. Serving resumes by itself once confirmations recover. |
| 400 | bad_request | The body is not JSON, or UserId is missing or not a positive number. |
| 400 | bad_key | No Authorization header, or the key is wrong. |
| 400 | bad_bot | The Bot field is missing or does not look like a bot username. |
| 400 | bot_taken | A bot with this username is already connected to HiViews on its own (with or without a token). To run it through the network, delete its own connection in the dashboard. |
| 400 | too_many_bots | The network has more than 50,000 bots — contact support. |
| 403 | bot_paused | The network is paused in the dashboard. |
| 403 | bot_moderation, bot_banned | The network is under review or blocked — contact support. |
What is paid and how
- Impressions. Every impression of a paid campaign whose sending Telegram confirmed is paid — welcome and regular. The rate per impression without a token is lower than for bots connected with a token.
- Clicks. On pay-per-click campaigns — $0.04 per click. The first click of an impression is paid, at most one per address per hour.
- External ads. Shown when no campaign of the network fits, so the bot is never idle. Paid per click only, the price depends on country and audience, the income is credited the next day.
- Withdrawals. The income of all bots of the network goes to one balance. It becomes withdrawable after 7 days (anti-fraud check); withdrawals from $50 in USDT (TRC20), withdrawal fee: $3. See the finance guide.
The statistics in the network card show impressions, clicks and income of all bots together.
Quality control
Every link in an ad goes through our tracking, and we know how often the same campaigns are clicked in bots where we deliver the ads ourselves. So for every bot and for the network as a whole we know how many clicks such a number of impressions should bring.
- Many impressions with almost no clicks, or with implausibly many, switch paid impressions off automatically — for that bot, or for the whole network if the mismatch shows across it. Pay-per-click stays.
- Clicks are counted by origin: many links opened from one address are one click.
- Income from fake impressions is returned to advertisers. Do not request ads for made-up
UserIds and do not send posts to service chats.
Limits
| What | Value |
|---|---|
| Requests per minute for one bot | 1,200 |
| Requests per minute for the whole network | 120,000 |
| Bots in a network | up to 50,000 |
| Time to send a post after receiving it | 3 minutes |
| Recommended request timeout | 5 seconds |
Need more? Contact support — the limits can be raised.
Checking the integration
The request goes through
Send the example request with your key and the username of one of your bots.
"ok": truewith anadobject, or"ok": falsewithno_ads, both mean the key and the bot name are accepted.The post arrives whole
Press /start in the bot from your own account. The ad should arrive with its button and, if the post has a picture or a video, with it.
The button goes through hiviews.net
The button link starts with
https://hiviews.net/api/go/. If it does not, the code changes links and clicks will not be counted.The impression shows up in the statistics
A minute later open the network card in the dashboard: the impression should be in the statistics. If it is not, the post was changed or link previews are off.
The regular impression comes after the result
Make a request in the bot and make sure the ad arrives after the result, not before it.
Network settings
Settings are made once in the network card in the dashboard and apply to all its bots.
- Formats: the welcome or the regular impression can be switched off.
- Ad categories: you can refuse any — betting, VPN, dating and others.
- External ads and pay-per-click ads are switched on and off separately.
- Pause stops ads in all bots of the network at once.
- The network's topics are set by support when the network is created; to change them, contact support.
FAQ
Do I have to share bot tokens?
No. The bots send the ads themselves, with their own tokens, which stay with you. We only need the network key in the request header.
How do I add a new bot to the network?
You do not add it separately: start sending requests with its username in the Bot field. The bot registers on its first request and gets ads right away.
Can I use the network key for a bot that is already connected on its own?
No, the request returns bot_taken. Delete the bot's own connection in the dashboard — after that it works through the network. Keep in mind that a bot connected with a token has a higher rate per impression.
Why are there impressions but no payment for them?
Impressions confirmed by Telegram are paid. Check that the post is sent unchanged, with link previews on, and within 3 minutes of receiving it.
What happens if one bot of the network breaks the rules?
Paid impressions are switched off for that bot; the others keep working. If the mismatch between impressions and clicks shows across the network, paid impressions are switched off for the whole network.
Need a network key or have questions? A network is set up on request: tell support how many bots you have and what they are about.