How to connect your bot
-
Connect your bot in the dashboard
In the web dashboard click “Connect a bot” and paste your token from @BotFather. The token is sent over HTTPS, stored encrypted and never shown back.
-
Wait for moderation
We review your bot. While it is under moderation, the API responds with
403 The bot on moderation. -
Configure ads
Propose your bot’s topics (moderators approve them), optionally switch off unwanted ad categories, and allow or disallow explicit posts (within Telegram’s rules). The ad language is matched automatically to each user’s Telegram language.
-
Get your API key
The key is shown once — right after you connect the bot; you can issue a new one in the bot’s settings in the web dashboard. Keep it on your server and never expose it in client code.
Request
POST https://hiviews.net/ with a JSON body.
Headers
| Header | Value |
|---|---|
Authorization | The API key as is, without a Bearer prefix |
Content-Type | application/json |
Body fields
| Field | Type | Description |
|---|---|---|
UserId | int | Telegram user ID. Private chats only — groups and channels are not supported. |
MessageId | int | ID of the user’s message — the ad is sent as a reply to it. |
UserFirstName | string | null | User’s first name, replaces $name in the post text. May be null — then we take the name from Telegram. $username in a post becomes the viewer’s @username (or their name if they have none) — no need to send it. |
StartPlace | bool, optional | true or omitted — welcome impression, send it on /start. false — regular impression elsewhere in the bot. |
Example body
{
"UserId": 123456789,
"MessageId": 45,
"UserFirstName": "Ivan",
"StartPlace": true
}When to call
- Welcome impression — as the first line of your
/starthandler (StartPlace: trueor omitted). Wait for the response: on success the API replies after about 2 seconds — that gives the user time to read the ad, and then your bot shows its own greeting. - Regular impression — in response to a real user action (
StartPlace: false), after your bot has delivered the result: first send the user what they came for (the video, file or answer), then request the ad. That way the post is the last message in the chat. If the ad arrives before the result, the result pushes it out of sight: such impressions barely get clicks, and the bot receives fewer pay-per-click ads. - The same rules apply to bots and Mini Apps.
Frequency limits: a user gets at most one ad per hour across the whole network (plus a daily cap), and a welcome impression in a given bot at most once a day. Extra calls simply return No posts found for this user.
Requests are allowed in private chats only, and only in response to a real /start (first line of the handler) or real user activity.
Never send requests without a user message, never answer old /start commands, and never fake parameters (UserId, MessageId, StartPlace). Violations get the bot or Mini App banned and its earnings cancelled; impressions to fake and empty accounts are not paid.
Responses
The response body is plain text, not JSON. Rely on the HTTP status code and the text.
| Code | Response text | Meaning |
|---|---|---|
| 200 | Post sent! | The ad post was sent to the user. The reply arrives after ~2 s — the reading window for the ad. |
| 400 | No posts found for this user | No matching ad for this user right now. This is normal — just ignore it. |
| 400 | JSON not found… | The request body is empty or not JSON. |
| 400 | "UserId" field not found… | The UserId field is missing. |
| 400 | "MessageId" field not found… | The MessageId field is missing. |
| 400 | "UserFirstName" field not found… | The UserFirstName field is missing (send at least null). |
| 400 | "Place" field not found… | The placement could not be determined — check the StartPlace field (bool). |
| 400 | Chat type must be private (not group) | Ads are sent to private chats only. |
| 400 | API key not found… | The Authorization header is missing or the key is invalid. |
| 400 | Invalid BotFather token! (Refresh it in t.me/HiViewsBot) | The bot token was revoked or changed — update it in @HiViewsBot (“Update token” on the bot’s card). |
| 400 | Telegram error text, e.g. Forbidden: bot was blocked by the user | Telegram refused to deliver — usually the user has blocked the bot. |
| 403 | The bot on moderation | The bot is still under moderation. |
| 403 | The bot is blocked | The bot is blocked — contact support. |
| 403 | The bot is disabled (enable in t.me/HiViewsBot) | Ads are turned off — enable them in the web dashboard. |
Code samples
curl -X POST https://hiviews.net/sendMessage \
-H "Authorization: <API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"UserId": 123456789,
"MessageId": 45,
"UserFirstName": "Ivan",
"StartPlace": true
}'import asyncio
import logging
import httpx
from aiogram import Bot, Dispatcher, F, Router
from aiogram.filters import CommandStart
from aiogram.types import Message
HIVIEWS_URL = "https://hiviews.net/sendMessage"
HIVIEWS_KEY = "<API_KEY>"
router = Router()
router.message.filter(F.chat.type == "private") # private chats only
http = httpx.AsyncClient(timeout=5.0)
async def show_ad(message: Message, start_place: bool) -> None:
# On success the API replies after ~2 s: that is the reading window for the ad.
try:
resp = await http.post(
HIVIEWS_URL,
headers={"Authorization": HIVIEWS_KEY},
json={
"UserId": message.from_user.id,
"MessageId": message.message_id,
"UserFirstName": message.from_user.first_name,
"StartPlace": start_place,
},
)
# "No posts found" means no matching ad right now; this is normal
if resp.status_code != 200 and resp.text != "No posts found for this user":
logging.warning("HiViews: %s %s", resp.status_code, resp.text)
except httpx.HTTPError as e:
logging.warning("HiViews request failed: %s", e)
@router.message(CommandStart())
async def on_start(message: Message) -> None:
await show_ad(message, start_place=True) # first line, wait for the reply
await message.answer("Hi! How can I help?") # then your own greeting
async def main() -> None:
bot = Bot("<BOT_TOKEN>")
dp = Dispatcher()
dp.include_router(router)
try:
await dp.start_polling(bot)
finally:
await http.aclose()
if __name__ == "__main__":
asyncio.run(main())import logging
import requests
HIVIEWS_URL = "https://hiviews.net/sendMessage"
HIVIEWS_KEY = "<API_KEY>"
def show_ad(user_id: int, message_id: int, first_name: str | None,
start_place: bool = True) -> None:
# On success the API replies after ~2 s: that is the reading window for the ad.
try:
resp = requests.post(
HIVIEWS_URL,
headers={"Authorization": HIVIEWS_KEY},
json={
"UserId": user_id,
"MessageId": message_id,
"UserFirstName": first_name,
"StartPlace": start_place,
},
timeout=5,
)
if resp.status_code != 200 and resp.text != "No posts found for this user":
logging.warning("HiViews: %s %s", resp.status_code, resp.text)
except requests.RequestException as e:
logging.warning("HiViews request failed: %s", e)
# in your /start handler (private chat), as the first line, awaiting the reply:
# show_ad(user.id, message.message_id, user.first_name, start_place=True)
# send_greeting(user.id) # then your own greeting// Node.js 18+ (built-in fetch)
const HIVIEWS_URL = "https://hiviews.net/sendMessage";
const HIVIEWS_KEY = process.env.HIVIEWS_KEY; // <API_KEY>
// On success the API replies after ~2 s: that is the reading window for the ad.
async function showAd({ userId, messageId, firstName = null, startPlace = true }) {
try {
const res = await fetch(HIVIEWS_URL, {
method: "POST",
headers: { "Authorization": HIVIEWS_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
UserId: userId,
MessageId: messageId,
UserFirstName: firstName,
StartPlace: startPlace,
}),
signal: AbortSignal.timeout(5000),
});
const text = await res.text();
if (res.status !== 200 && text !== "No posts found for this user") {
console.warn("HiViews:", res.status, text);
}
} catch (err) {
console.warn("HiViews request failed:", err.message);
}
}
// in your /start handler (private chat), as the first line, awaiting the reply:
// await showAd({ userId: ctx.from.id, messageId: ctx.message.message_id,
// firstName: ctx.from.first_name, startPlace: true });
// await ctx.reply("Hi! How can I help?"); // then your own greeting<?php
const HIVIEWS_KEY = '<API_KEY>';
// On success the API replies after ~2 s: that is the reading window for the ad.
function hiviews_show_ad(int $userId, int $messageId, ?string $firstName, bool $startPlace = true): void
{
$ch = curl_init('https://hiviews.net/sendMessage');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: ' . HIVIEWS_KEY,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'UserId' => $userId,
'MessageId' => $messageId,
'UserFirstName' => $firstName,
'StartPlace' => $startPlace,
]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code !== 200 && $body !== 'No posts found for this user') {
error_log("HiViews: $code $body");
}
}
// in your /start handler (private chat), as the first line, awaiting the reply:
// $msg = $update['message'];
// hiviews_show_ad($msg['from']['id'], $msg['message_id'], $msg['from']['first_name'] ?? null, true);
// send_greeting($msg['chat']['id']); // then your own greetingPost sent!
Connecting without a token
If you would rather not share the bot token, connect the bot in the dashboard in the “Without a token” mode. You prove the bot is yours with a code in its description (Edit About in @BotFather) and get an API key. From then on the bot requests an ad and sends it to the user itself.
- Paid for confirmed impressions and for clicks. A bot without a token gets every campaign of the network from the start — welcome and regular impressions, pay-per-impression and pay-per-click — plus external ads; there is no separate approval to wait for. Each impression whose sending Telegram itself confirmed is paid, so send the post exactly as you received it. Clicks on pay-per-click ads are paid at $0.04 per click. The rate per impression without a token is lower than with one: with a token we send the ad ourselves and no confirmation is needed.
- Request:
POST https://hiviews.net/api/partner/adwith the headerAuthorization: API key. Fields:UserId(required),UserFirstName,Username,Language(the user’s Telegram language),StartPlace—trueon /start,falseafter delivering the result. - The answer is always JSON. If there is an ad:
"ok": trueand anadobject — send it to the user as is. If there is none:"ok": false, just carry on. - Do not change the post. Button links go through hiviews.net — that is how clicks are counted. A changed link is not paid.
- Delivery confirmation. The text starts with an invisible link: when the bot sends the post, Telegram itself requests it — that is how we know the post really went out. So do not disable link previews (
disable_web_page_preview) and do not strip links from the text. An unconfirmed post is not paid: neither the impression nor the clicks on it. delete_after— if present, delete the post after that many seconds.- Switching to the full mode. When you are ready, paste the bot token on the bot page in the dashboard. No code changes: the same request keeps working, we start sending the ads ourselves (the answer comes without
ad), and every impression is paid at the token rate, with no confirmation needed. - Anti-fraud: every link in the ad goes through HiViews tracking, and we know how often the same campaigns are clicked in bots with a token. A bot whose clicks do not match its impressions (far fewer or far more than expected) automatically loses paid impressions and goes back to pay-per-click only; a bot that does not really send most of the posts it takes stops getting ads. At most one click per IP address per hour is paid; payment pauses when the click rate is implausibly high.
- Bot networks. The full guide is on the Bot networks page. If you run many bots, you do not connect each of them: on request to support we issue one key for the whole network. The request is the same plus the
Botfield — the username of the bot the ad is for (without @):{"UserId": 123, "UserFirstName": "Ivan", "Language": "en", "StartPlace": true, "Bot": "my_first_bot"}. A bot registers on its first request; statistics are kept per bot and for the network as a whole. The answersbad_bot(the name does not look like a bot username) andbot_taken(this bot is already connected by someone on their own) mean no ads can be fetched for that name.
Response
{
"ok": true,
"ad": {
"text": "Ivan, look at this…",
"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
}
}Ready code: Python, aiogram 3
import asyncio, aiohttp
from aiogram import Bot
from aiogram.types import InlineKeyboardMarkup
HIVIEWS_KEY = "YOUR_API_KEY"
async def show_ad(bot: Bot, user, start: bool = False) -> None:
payload = {"UserId": user.id, "UserFirstName": user.first_name, "Username": user.username,
"Language": user.language_code, "StartPlace": start}
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
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) # keep link previews on
if ad.get("delete_after"):
asyncio.get_running_loop().call_later(
ad["delete_after"], lambda: asyncio.ensure_future(quiet_delete(bot, user.id, msg.message_id)))
except Exception:
pass
async def quiet_delete(bot: Bot, chat_id: int, message_id: int) -> None:
try:
await bot.delete_message(chat_id, message_id)
except Exception:
passReady code: Node.js, Telegraf
// HiViews without a token: the bot sends the ad itself, paid for confirmed impressions and clicks. Node.js 18+, Telegraf.
// Send the post as is: Telegram confirms the delivery to us through the hidden link in the text.
const HIVIEWS_KEY = "YOUR_API_KEY";
async function showAd(ctx, start = false) {
// Call showAd(ctx, true) on /start and showAd(ctx, false) after delivering the 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 }),
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); // keep link previews on
if (ad.delete_after) {
setTimeout(() => ctx.telegram.deleteMessage(u.id, msg.message_id).catch(() => {}), ad.delete_after * 1000);
}
} catch (e) { /* an ad must never break the bot */ }
}
// Example: bot.start(async (ctx) => { await showAd(ctx, true); /* your greeting */ });Ready code: PHP
<?php
// HiViews without a token: the bot sends the ad itself, paid for confirmed impressions and clicks.
// Send the post as is: Telegram confirms the delivery to us through the hidden link in the text.
const HIVIEWS_KEY = 'YOUR_API_KEY';
/** $user is the `from` array of the Telegram update. On /start: $start = true, after the 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, 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,
]),
]);
$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']]; // keep link previews on
}
$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, $update['message']['from'], true); // in the /start handlerTips
- Call the API as the first line of your
/starthandler and await the response. A successful response arrives after about 2 seconds — that is the reading window for the ad; send your greeting after it. - Use a 5-second timeout. On network errors don’t retry — just carry on with your bot’s normal flow.
400 No posts found for this usercomes back immediately — handle it silently and show your greeting.- Log other errors:
403needs action in the web dashboard, token errors — in @HiViewsBot. - Keep your API key on the server, in environment variables.
Earnings & payouts
- $4.80 per 1,000 welcome and $2.40 per 1,000 regular impressions.
- $0.04 per click from pay-per-click ads. They go to your bot only when, at its CTR, they pay more than impressions. Same integration — no API changes.
- Earnings become available for withdrawal after 7 days.
- Withdrawals in USDT (TRC20) from $50, $3 fee — request in the web dashboard, confirm in the Telegram bot.