Rich delivery
Integration
With the SDK it's three lines: declare formats on inject(). Use the HTTP rich-delivery guide for ephemeral messages in groups.
httpx — raw REST
# session is an httpx.AsyncClient; tg raises on a Telegram error.API = "https://sidekick-ads.com"AUTH = {"Authorization": "Bearer sk_live_xxxxx"}PLT = "plt_xxxxx"CAPS = {"rich_messages": True, "callbacks": True, "custom_emoji": False}# Private chat, plain text. Send the original reply even if the ad fails.async def reply_with_ad(session, tg, chat_id, user_id, reply, lang):await tg("sendMessage", {"chat_id": chat_id, "text": reply})try:response = await session.post(f"{API}/api/v1/ad", headers=AUTH, timeout=3.0, json={"user_id": user_id, "message": reply, "platform_id": PLT,"language_code": lang or "en","accept_formats": ["text", "response", "card", "consent", "quiz"],"capabilities": CAPS,},)response.raise_for_status()data = response.json()if not data.get("has_ad"):returnad = data["ad"]send = ad.get("send")if send:try:await tg(send["method"], {**send["params"], "chat_id": chat_id})returnexcept Exception:pass # Try the text ad below; do not request another impression.fallback = ad.get("fallback") or adawait tg("sendMessage", {"chat_id": chat_id, "text": "Ad\n" + fallback["text"],"reply_markup": {"inline_keyboard": [[{"text": fallback["button_text"], "url": fallback["button_url"],}]]},})except Exception:pass # Log the ad delivery failure; the bot reply was already sent.# Manual HTTP callback handler. session is an httpx.AsyncClient.# tg(method, params) must raise if Telegram rejects the call.async def on_sidekick_callback(session, tg, callback, api_key, capabilities):data = callback.get("data", "")if not data.startswith("sk:"):return Falsetry:_, token, payload = data.split(":", 2)message = callback["message"]chat = message["chat"]response = await session.post("https://sidekick-ads.com/api/v1/ad/interact",headers={"Authorization": f"Bearer {api_key}"},json={"token": token,"payload": payload,"user_id": callback["from"]["id"],"chat_type": chat["type"],"callback_query_id": callback["id"],"ephemeral_message_id": message.get("ephemeral_message_id"),"capabilities": capabilities,},timeout=3.0,)response.raise_for_status()delivery = response.json()["send"]method = delivery["method"]params = dict(delivery["params"])params.setdefault("chat_id", chat["id"])if method == "editMessageText":params.setdefault("message_id", message["message_id"])await tg(method, params)except Exception:# Report the failure in your logger without credentials or payloads.passfinally:try:await tg("answerCallbackQuery", {"callback_query_id": callback["id"],})except Exception:passreturn True
Client requirements
- •Rich blocks render on Telegram clients released 25.08.2026 or later. Older clients degrade softly: some blocks disappear, but the text and the classic
reply_markupCTA button (always duplicated by the server) stay visible. - •Custom-emoji brand logos render only when your bot owner has Telegram Premium — declare
capabilities.custom_emojiaccordingly. Without it the logo degrades to a bold brand name. - •Groups: interactive scenes after a tap are always delivered ephemerally (visible only to the tapping user). Scene 1 itself is ephemeral only when your bot is a chat admin (
bot_is_admin: true); otherwise it's a regular message — Telegram allows unprompted ephemeral sends to admin bots only.