Bot API

Telegram bot monetization API

Connect your bot to HiViews with a single HTTP request: send it when a user messages your bot, and HiViews delivers a matching ad post to them. You earn on every impression.

POST https://hiviews.net/sendMessage

How to connect your bot

  1. 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.

  2. Wait for moderation

    We review your bot. While it is under moderation, the API responds with 403 The bot on moderation.

  3. 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.

  4. 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/sendMessage with a JSON body.

Headers

HeaderValue
AuthorizationThe API key as is, without a Bearer prefix
Content-Typeapplication/json

Body fields

FieldTypeDescription
UserIdintTelegram user ID. Private chats only — groups and channels are not supported.
MessageIdintID of the user’s message — the ad is sent as a reply to it.
UserFirstNamestring | nullUser’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.
StartPlacebool, optionaltrue 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 /start handler (StartPlace: true or 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.

CodeResponse textMeaning
200Post sent!The ad post was sent to the user. The reply arrives after ~2 s — the reading window for the ad.
400No posts found for this userNo matching ad for this user right now. This is normal — just ignore it.
400JSON 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).
400Chat type must be private (not group)Ads are sent to private chats only.
400API key not found…The Authorization header is missing or the key is invalid.
400Invalid 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).
400Telegram error text, e.g. Forbidden: bot was blocked by the userTelegram refused to deliver — usually the user has blocked the bot.
403The bot on moderationThe bot is still under moderation.
403The bot is blockedThe bot is blocked — contact support.
403The 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
  }'
Successful response (~2 s): 200 Post 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/ad with the header Authorization: API key. Fields: UserId (required), UserFirstName, Username, Language (the user’s Telegram language), StartPlace — true on /start, false after delivering the result.
  • The answer is always JSON. If there is an ad: "ok": true and an ad object — 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 Bot field — 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 answers bad_bot (the name does not look like a bot username) and bot_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:
        pass

Ready 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 handler

Tips

  • Call the API as the first line of your /start handler 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 user comes back immediately — handle it silently and show your greeting.
  • Log other errors: 403 needs 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.

or in the Telegram bot @HiViewsBot