401Ключ неверный или заменён. Создайте новый ключ в карточке бота.
EVIR
EVIRПолучайте карточки через /next, отправляйте их пользователю и подтверждайте успешную отправку.
Нужны два значения и один обработчик команды. Начните с готового примера ниже.
CONNECTION_IDEVIR_API_KEY — имя переменной окружения. Сам ключ храните только на сервере; ссылка на эту страницу его не содержит.
import os
import time
import uuid
import requests
from urllib.parse import quote
EVIR_URL = "https://evir.bot/api/v1"
EVIR_KEY = os.environ["EVIR_API_KEY"]
EVIR_CONNECTION_ID = os.getenv("EVIR_CONNECTION_ID", "CONNECTION_ID")
session = requests.Session()
class EvirError(RuntimeError):
def __init__(self, message, status=None, code=None):
super().__init__(message)
self.status = status
self.code = code
def evir_post(path, payload, operation_id=None):
operation_id = operation_id or str(uuid.uuid4())
for attempt in range(3):
try:
response = session.post(
f"{EVIR_URL}/integrations/{EVIR_CONNECTION_ID}{path}",
headers={
"Authorization": f"Bearer {EVIR_KEY}",
"Content-Type": "application/json",
"Idempotency-Key": operation_id,
},
json=payload,
timeout=(3, 10),
)
except requests.RequestException:
if attempt == 2:
raise
time.sleep(0.5 * (2 ** attempt))
continue
if response.status_code == 429 and attempt < 2:
try:
delay = max(1.0, float(response.headers.get("Retry-After", "1")))
except ValueError:
delay = 1.0
time.sleep(delay)
continue
if response.status_code >= 500 and attempt < 2:
time.sleep(0.5 * (2 ** attempt))
continue
try:
body = response.json()
except ValueError:
body = {}
if not response.ok:
error = body.get("error", {})
raise EvirError(
error.get("message", f"EVIR: HTTP {response.status_code}"),
status=response.status_code,
code=error.get("code"),
)
return body["data"]
# Вызывайте при /offers или в другом добровольном месте показа.
def get_sponsors(telegram_user_id, is_premium=False):
data = evir_post("/next", {
"recipientId": str(telegram_user_id),
"limit": 3,
"isPremium": bool(is_premium),
})
return data["assignments"] # [] — сейчас подходящих предложений нет
# Не включайте parse_mode: title и description показываются обычным текстом.
def to_telegram_card(assignment):
keyboard = [[{
"text": assignment["ctaLabel"],
"url": assignment["actionUrl"], # используйте URL без изменений
}]]
if assignment.get("requiresQualification"):
keyboard.append([{
"text": "Проверить",
"callback_data": f'evir_qualify:{assignment["deliveryId"]}',
}])
return {
"text": f'{assignment["title"]}\n\n{assignment["description"]}',
"reply_markup": {"inline_keyboard": keyboard},
}
# /served подтверждает отправку карточки: вызывайте только после успешного ответа Telegram.
def confirm_card_served(delivery_id):
return evir_post(
f"/deliveries/{quote(str(delivery_id), safe='')}/served",
{},
)
# Обработчик callback-кнопки проверки: подтвердить действие пользователя в ОП канала.
def qualify_delivery(telegram_user_id, delivery_id):
return evir_post(
f"/deliveries/{quote(str(delivery_id), safe='')}/qualify",
{"recipientId": str(telegram_user_id)},
)
# В production храните локальное состояние выдачи: allocated → sending → sent → acked.
# Если результат Telegram неопределён, не отправляйте эту карточку повторно.
# После подтверждённой отправки повторяйте только EVIR /served, пока не получите ack.
# Если sendTelegram завершился ошибкой, /served не вызывается.
def send_sponsor_card(send_telegram, telegram_user_id, assignment):
send_telegram(telegram_user_id, to_telegram_card(assignment))
needs_served_ack = (
assignment.get("productType") in ("impression", "transition")
or (
assignment.get("productType") == "op"
and assignment.get("targetType") == "bot"
)
)
if needs_served_ack:
return confirm_card_served(assignment["deliveryId"])
return None
# Только для legacy-карточки вызывайте при Telegram-команде /start <параметр>.
def confirm_target_bot_start(telegram_user_id, start_param):
return evir_post("/start", {
"recipientId": str(telegram_user_id),
"startParam": str(start_param),
})Задайте EVIR_API_KEY и EVIR_CONNECTION_ID в переменных окружения сервера.
В обработчике /offers вызовите POST /next и отправьте каждую полученную карточку.
Только после успешного ответа Telegram вызовите /served, если формат требует это по таблице ниже.
Запустите /offers в Telegram и затем нажмите «Проверить подключение» в EVIR.
| Формат | Вызов | Когда засчитывается |
|---|---|---|
| Показ | /served | После успешной отправки |
| Переход | /served | После открытия ссылки через EVIR |
| ОП бота | /served | После настоящего /start целевого бота |
| ОП канала | /qualify | После подтверждения подписки или заявки |
Передавайте actionUrl без изменений. Если requiresQualification=true, покажите кнопку «Проверить» и вызовите /qualify с тем же recipientId.
401Ключ неверный или заменён. Создайте новый ключ в карточке бота.
403SDK_ADS_NOT_TRUSTED — платные форматы для подключения ещё не включены. Обратитесь в поддержку EVIR.
404Проверьте ID подключения или deliveryId.
429Подождите время из заголовка Retry-After.
5xxПовторите запрос с задержкой и тем же Idempotency-Key.
Лимит: 120 запросов в минуту на подключение. Для сетевых ошибок используйте таймаут и не более нескольких повторов.
Добавьте бота в разделе «Заработок» и выберите API. EVIR покажет ключ один раз, а ID останется в карточке бота. Если бот уже добавлен, откройте его карточку — повторно добавлять его не нужно.
Ключ появляется один раз после успешного подключения к своему коду. Если окно было закрыто, откройте карточку бота и создайте новый ключ.
Подключение работает, но платные форматы для него ещё не включены. Обратитесь в поддержку EVIR.
Старый ключ повторно посмотреть нельзя. Нажмите «Создать новый ключ EVIR» в карточке бота и сразу сохраните его в секретах сервера. Предыдущий ключ перестанет работать.
Откройте карточку бота и раздел подключения. Повторно добавлять бота и вводить Telegram-токен не нужно.
EVIR_API_KEY и EVIR_CONNECTION_ID заданы на сервере/offers возвращает или корректно обрабатывает пустой assignments