Вебхуки¶
Когда пользователь отправляет боту текст, геолокацию или контакт, BotFarm2 пересылает данные на текущую страницу пользователя POST-запросом.
Формат запроса¶
POST /your-page-url HTTP/1.1
Content-Type: application/json
{
"text": "сообщение от пользователя",
"location": null,
"contact": null
}
Всегда присутствуют все три поля. Заполнено только одно из них — в зависимости от типа действия пользователя.
Типы событий¶
Текстовое сообщение¶
Пользователь отправил текст боту.
Геолокация¶
Пользователь поделился местоположением.
Контакт¶
Пользователь поделился номером телефона (в ответ на .bf-request-contact).
{
"text": null,
"location": null,
"contact": {
"phone_number": "+79001234567",
"first_name": "Иван",
"last_name": "Иванов",
"user_id": 123
}
}
| Поле | Тип | Описание |
|---|---|---|
phone_number |
string | Номер телефона в международном формате |
first_name |
string | Имя пользователя из мессенджера |
last_name |
string | Фамилия (может быть пустой) |
user_id |
int | ID пользователя в BotFarm (BFUser.id) |
Файл¶
Пользователь отправил файл боту (Telegram).
Файлы отправляются как multipart/form-data, а не JSON.
Заголовки запроса¶
Каждый запрос к вашему бекенду содержит заголовки с информацией о пользователе:
| Заголовок | Описание |
|---|---|
Botfarm-Hook-Secret |
Секретный токен компании — см. Проверка подлинности запросов |
Botfarm-ID |
ID пользователя в BotFarm (BFUser.id) |
Botfarm-User-ID |
Алиас для Botfarm-ID |
Botfarm-First-Name |
Имя пользователя (URL-encoded) |
Botfarm-Last-Name |
Фамилия (URL-encoded) |
Botfarm-Username |
Username |
Botfarm-Type |
Платформа: tg, vk, max, ig |
Botfarm-Page |
Текущая страница пагинации кнопок |
Botfarm-Tg-Ext-ID |
Telegram ID (если есть) |
Botfarm-VK-Ext-ID |
VK ID (если есть) |
Botfarm-Max-Ext-ID |
Max ID (если есть) |
Botfarm-Avatar-URL |
URL аватара пользователя (если есть) |
Проверка подлинности запросов¶
Заголовки Botfarm-ID, Botfarm-Tg-Ext-ID и остальные — это обычные HTTP-заголовки,
их может подделать кто угодно, кто знает URL вашей страницы. Чтобы отличить настоящий
запрос от BotFarm2 от подделки, используйте заголовок Botfarm-Hook-Secret.
В нём приходит секретный токен вашей компании (hook_secret_token). Вы получаете его:
- в ответе на
POST /createCompanyBot— полеhook_secret_token; - либо в админке BotFarm2 в карточке компании (поле «Секретный токен для хука»).
Токен один на компанию и одинаков для всех её ботов и страниц.
Заголовок отправляется при каждом обращении BotFarm2 к вашему бекенду:
- GET-запросы за HTML страницы (навигация пользователя,
/navigate,/refresh); - POST-запросы с текстом, геолокацией, контактом и файлами;
- запросы на
fallback_urlбота; - запросы предпросмотра страницы из админки BotFarm2.
Проверяйте токен на каждом запросе
Без проверки любой, кто узнает URL страницы, сможет подставить чужой Botfarm-ID
и получить или изменить данные другого пользователя.
Пример на Django¶
import secrets
from django.conf import settings
from django.http import HttpResponseForbidden
def check_botfarm_secret(request):
"""Возвращает True, если запрос действительно пришёл от BotFarm2."""
received = request.META.get("HTTP_BOTFARM_HOOK_SECRET", "")
return secrets.compare_digest(received, settings.BOTFARM_HOOK_SECRET)
def my_page(request):
if not check_botfarm_secret(request):
return HttpResponseForbidden("Invalid hook secret")
...
Сравнивайте токены через secrets.compare_digest (или аналог в вашем языке), а не через
== — обычное сравнение строк уязвимо к timing-атакам. Сам токен храните в переменных
окружения, а не в коде.
Пример на FastAPI¶
import os
import secrets
from fastapi import Header, HTTPException
BOTFARM_HOOK_SECRET = os.environ["BOTFARM_HOOK_SECRET"]
async def verify_botfarm_secret(botfarm_hook_secret: str = Header(default="")):
if not secrets.compare_digest(botfarm_hook_secret, BOTFARM_HOOK_SECRET):
raise HTTPException(status_code=403, detail="Invalid hook secret")
403 — особый статус
Если ваш бекенд вернёт 403, BotFarm2 считает, что пользователь заблокирован на вашей стороне, и молча пропускает запрос — пользователю ничего не отправляется и ошибка в сервисный чат не уходит. Это удобно для бана пользователей, но означает, что неверно настроенный секрет проявится как «бот молчит». Если хотите видеть проблему явно — возвращайте 400 или 500.
Пример обработки на Django¶
import json
def my_page(request):
if request.method == "POST":
# Файл
if request.FILES:
uploaded_file = request.FILES["file"]
# Обработка файла...
return render(request, "result.html")
# JSON данные (текст, геолокация, контакт)
data = json.loads(request.body)
if text := data.get("text"):
# Обработка текстового сообщения
...
if location := data.get("location"):
lat = location["latitude"]
lon = location["longitude"]
# Обработка геолокации
...
if contact := data.get("contact"):
phone = contact["phone_number"]
user_id = contact["user_id"]
# Обработка контакта
...
# GET — обычный рендер страницы
user_id = request.META.get("HTTP_BOTFARM_ID")
bot_type = request.META.get("HTTP_BOTFARM_TYPE")
return render(request, "my_page.html", {"user_id": user_id})
Ответ вашего бекенда¶
В ответ на POST-запрос бекенд должен вернуть HTML-страницу в том же формате, что и для GET-запросов. BotFarm2 распарсит HTML и отправит результат пользователю.
Обработка ошибок¶
Если ваш бекенд вернул HTTP-статус, отличный от 200:
- 405, 401, 403 — если у бота настроен
fallback_url, запрос перенаправляется туда - Другие ошибки — пользователю отправляется
bot.error_message, уведомление в сервисный чат
Суперпользователи видят подробный traceback ошибки.
Запрос контакта¶
Чтобы запросить номер телефона пользователя, используйте класс .bf-request-contact в HTML:
<div class="bf-message bf-request-contact" data-button-text="Поделиться номером">
<span class="bf-text">Для регистрации нам нужен ваш номер телефона</span>
</div>
| Атрибут | Обязательный | Описание |
|---|---|---|
class="bf-message bf-request-contact" |
Да | Тип сообщения |
data-button-text |
Нет | Текст кнопки (по умолчанию: "Отправить номер телефона") |
Пользователь увидит сообщение с кнопкой. При нажатии мессенджер запросит подтверждение, после чего BotFarm2 отправит POST-запрос с полем contact на текущую страницу.