Перейти к содержимому
Socialift
Разделы документации
Техническое руководство

Приём вебхуков с помощью SDK

Endpoint вебхука делает три вещи: проверяет подлинность запроса, быстро возвращает 200 и ставит тяжёлую работу в очередь. Meta и TikTok подписывают тело HMAC-SHA256, поэтому подпись проверяется по сырым байтам; Telegram тело не подписывает, а только присылает заголовок X-Telegram-Bot-Api-Secret-Token. В модуле socialift.webhooks есть готовые функции для всех трёх случаев.

Обновлено:

Коротко: что должен делать endpoint вебхука?

Для Meta: на GET верните hub.challenge обычным текстом через verify_challenge(); на POST прочитайте сырое тело, проверьте X-Hub-Signature-256 через verify_request(), разбейте на события через parse_events(), поставьте в очередь и верните 200. Для TikTok — verify_tiktok_request() и parse_tiktok_event(), для Telegram-бота — verify_telegram_request(). Повторные доставки отсекайте по dedupe_key() или event.key.

Эта страница — техническая инструкция. Общее описание возможностей вебхуков Socialift — на странице «Интеграция вебхуков».

Как платформы подписывают вебхуки?

Как платформы подписывают вебхуки? — Таблица возможностей по платформам
ПлатформаЗаголовокПроверкаПовторная доставкаФункции SDK
Meta (Facebook, Instagram)X-Hub-Signature-256: sha256=<hex>HMAC-SHA256: ключ — App Secret, сообщение — сырое телоСразу, затем с убывающей частотой в течение 36 часовverify_challenge, verify_request, parse_events, dedupe_key
TikTokTiktok-Signature: t=<unix>,s=<hex>HMAC-SHA256: ключ — Client Secret, сообщение — <t>.<сырое тело>До 72 часов с экспоненциальным backoffverify_tiktok_request, parse_tiktok_event
Telegram Bot APIX-Telegram-Bot-Api-Secret-TokenПодписи нет: сравнение с secret_token из setWebhookПовторяет при ответе не 2XX и прекращает после разумного числа попытокverify_telegram_request

Правила повторной доставки взяты из документации платформ и могут меняться — источники в конце страницы.

FastAPI: вебхук Meta — подтверждение и события

meta_webhook.py
import asyncio
import json
import os
from fastapi import FastAPI, Request, Response
from socialift.webhooks import parse_events, verify_challenge, verify_request

app = FastAPI()
events: asyncio.Queue = asyncio.Queue()   # в продакшене: Redis, RQ, Celery и т. п.


@app.get("/webhooks/meta")
async def meta_verify(request: Request):
    challenge = verify_challenge(dict(request.query_params), os.environ["META_VERIFY_TOKEN"])
    if challenge is None:
        return Response(status_code=403)
    return Response(challenge, media_type="text/plain")   # не JSON, а именно строка


@app.post("/webhooks/meta")
async def meta_receive(request: Request):
    body = await request.body()                           # сырые байты
    if not verify_request(os.environ["FACEBOOK_APP_SECRET"], body, request.headers):
        return Response(status_code=403)
    for event in parse_events(json.loads(body)):
        events.put_nowait(event)
    return Response(status_code=200)

verify_challenge() возвращает hub.challenge, если совпали hub.mode=subscribe и hub.verify_token, иначе None. Ответом должна быть ровно строка challenge.

verify_request() нужны сырые байты до разбора тела фреймворком — пересериализованный JSON ломает подпись. Нет заголовка или он пустой — результат False; сравнение идёт через hmac.compare_digest.

request.headers не зависит от регистра. Если передаёте обычный dict, ключ должен быть X-Hub-Signature-256 или x-hub-signature-256.

Каждую страницу подписывают отдельно: await fb.subscribe_page(page_id, page_token, fields=['leadgen', 'feed']). Meta требует для этого разрешения pages_manage_metadata и pages_show_list; без подписки события не придут, даже если webhook настроен на уровне приложения.

WebhookEvent: что возвращает parse_events()?

Meta присылает два формата: changes (leadgen, feed, comments) и messaging (например, сообщения Instagram Direct). parse_events() превращает оба в единый поток WebhookEvent.

object_type
Поле object из payload: например, page или instagram.
field
Значение field для changes (leadgen, comments, feed и т. д.); messages для записей messaging и messages_standby для standby.
object_id и time
entry.id (обычно ID страницы или аккаунта Instagram) и entry.time (unix).
value
Само событие: changes[].value или элемент messaging[].
leadgen_id, form_id, page_id
Удобные свойства для событий Lead Ads; если в value нет page_id, возвращается object_id.
key
Стабильный ключ для отсева повторных доставок — то же, что dedupe_key(event).

FastAPI: вебхук TikTok

tiktok_webhook.py
import json
import os
from fastapi import FastAPI, Request, Response
from socialift.webhooks import parse_tiktok_event, verify_tiktok_request

app = FastAPI()


@app.post("/webhooks/tiktok")
async def tiktok_receive(request: Request):
    body = await request.body()
    if not verify_tiktok_request(os.environ["TIKTOK_CLIENT_SECRET"], body, request.headers):
        return Response(status_code=403)
    event = parse_tiktok_event(json.loads(body))
    if event.event == "authorization.removed":
        print("Доступ отозван:", event.user_openid)   # удалите токены пользователя
    return Response(status_code=200)

TikTok присылает одно событие на запрос: event.event — имя события, event.user_openid — пользователь, event.create_time — unix-время. Поле content TikTok передаёт JSON-строкой; SDK разбирает его в словарь event.content, а если строка битая — оставляет её в {'raw': ...}.

tolerance (по умолчанию 300 секунд): если метка времени отличается от текущего времени больше чем на столько секунд, запрос отклоняется — это защита от повторной отправки старого запроса. Держите часы сервера синхронизированными; tolerance=0 отключает проверку и не рекомендуется.

В документации TikTok заголовок называется Tiktok-Signature. С request.headers проблем нет; если передаёте обычный dict, SDK ищет ключ TikTok-Signature или tiktok-signature.

event.key — ключ дедупликации по publish_id, video_id или comment_id, а если их нет — по хешу тела.

FastAPI: вебхук Telegram-бота

telegram_webhook.py
import os
from fastapi import FastAPI, Request, Response
from socialift import Telegram
from socialift.webhooks import verify_telegram_request

app = FastAPI()
SECRET = os.environ["TELEGRAM_WEBHOOK_SECRET"]    # 1–256 символов: A-Z, a-z, 0-9, _ и -


async def register_webhook():                      # один раз при деплое
    async with Telegram.from_env() as bot:
        await bot.set_webhook("https://example.uz/webhooks/telegram",
                              secret_token=SECRET, allowed_updates=["message"])


@app.post("/webhooks/telegram")
async def telegram_receive(request: Request):
    if not verify_telegram_request(request.headers, SECRET):
        return Response(status_code=403)
    update = await request.json()
    print(update.get("message", {}).get("text"))     # поставьте в очередь
    return Response(status_code=200)

Порядок аргументов отличается от Meta: verify_telegram_request(headers, secret_token). При пустом secret_token функция всегда возвращает False — чтобы webhook не остался открытым без секрета.

Telegram не подписывает тело, поэтому секрет — единственная защита: без него любой, кто знает адрес, может прислать поддельный update. Сравнение выполняется за постоянное время (compare_digest).

После установки webhook get_updates() не работает. Если обновления не приходят, смотрите last_error_message и pending_update_count в await bot.get_webhook_info().

Зачем быстро отвечать 200 и уносить работу в очередь?

Платформы повторяют доставку. Meta повторяет неудачную доставку сразу, а затем с убывающей частотой в течение 36 часов. TikTok повторяет до 72 часов с экспоненциальным backoff и прямо пишет, что одно событие может прийти несколько раз. Telegram повторяет запрос после ответа, отличного от 2XX.

Запись в CRM, загрузка файлов или медленный вызов API внутри обработчика задерживают ответ; если платформа сочтёт доставку неудачной, она пришлёт событие снова и дублей станет больше. Правильный порядок: проверить подпись → записать событие в очередь (Redis, RQ, Celery или таблица в БД) → сразу 200. Обработкой занимается отдельный воркер.

Meta может объединять несколько событий (до 1000) в один запрос, но не гарантирует этого — обрабатывайте каждое событие отдельно.

Идемпотентность: обработать каждое событие один раз

worker.py
import asyncio
from socialift.webhooks import WebhookEvent, dedupe_key

seen: set[str] = set()   # в продакшене: UNIQUE-колонка в БД или Redis SET NX


async def worker(events: asyncio.Queue):
    while True:
        event: WebhookEvent = await events.get()
        key = dedupe_key(event)                  # то же, что event.key
        if key not in seen:
            seen.add(key)
            if event.field == "leadgen" and event.leadgen_id:
                print("Новый лид:", event.leadgen_id, "форма:", event.form_id)
        events.task_done()

Meta не даёт ID события, поэтому dedupe_key() собирает ключ из содержимого: по leadgen_id, mid, comment_id, media_id или post_id, если они есть, иначе по хешу value, object_id и времени. Meta повторно присылает точно такое же тело, поэтому и запасной вариант ловит дубль.

set в памяти очищается при перезапуске и не делится между воркерами. В продакшене пишите ключ в таблицу с UNIQUE-колонкой (INSERT ... ON CONFLICT DO NOTHING) и обрабатывайте событие, только если вставка прошла.

Чек-лист для продакшена

  • Endpoint доступен только по HTTPS — TikTok требует HTTPS для callback URL.
  • Проверяйте подпись по сырым байтам до разбора тела; на неверную подпись отвечайте 403 и пишите в лог.
  • Храните App Secret, Client Secret, verify token и секрет Telegram в переменных окружения.
  • Возвращайте 200 сразу, обработку ставьте в очередь.
  • Храните ключ дедупликации в UNIQUE-колонке базы.
  • В Meta подписывайте каждую страницу через subscribe_page().
  • Callback удаления данных Meta приходит не с X-Hub-Signature-256, а с signed_request в теле POST — разбирайте его через socialift.crypto.parse_signed_request(signed_request, app_secret) и отвечайте 400, если вернулся None.

Адреса вебхуков в кабинете Socialift

Если вы подключаете аккаунты через кабинет Socialift и добавили своё Meta- или TikTok-приложение в разделе «Kalitlar» (ключи), после сохранения ключа кабинет покажет отдельные Webhook URL и verify token для этого приложения — их нужно скопировать в консоль платформы.

У каждого приложения свой адрес, потому что Meta и TikTok подписывают событие секретом именно этого приложения. Если вы принимаете вебхуки на своём сервере через SDK, достаточно примеров с этой страницы.

Частые вопросы

Подпись вебхука Meta не совпадает — в чём причина?

Обычно причин три: тело не сырое (фреймворк разобрал JSON и собрал заново), используется App Secret другого приложения или прокси меняет тело. Передавайте в verify_request() результат await request.body() и секрет того приложения, в котором настроен webhook.

Meta пишет «The URL couldn't be validated». Что делать?

Ответом на GET должна быть ровно строка hub.challenge — не JSON, а text/plain. Проверьте, что hub.verify_token совпадает со значением в консоли и endpoint доступен по HTTPS. verify_challenge() при несовпадении возвращает None — тогда отвечайте 403.

Подпись TikTok верная, но verify_tiktok_request() возвращает False. Почему?

Чаще всего из-за расхождения часов: если время сервера отличается от метки TikTok больше чем на 300 секунд, запрос отклоняется. Синхронизируйте часы через NTP. Также проверьте, что client_secret не пустой, и передавайте заголовки как request.headers.

Как защитить вебхук Telegram?

Передайте secret_token в set_webhook() и проверяйте каждый запрос через verify_telegram_request(request.headers, secret). Telegram возвращает эту строку в заголовке X-Telegram-Bot-Api-Secret-Token. Секрет — от 1 до 256 символов, допускаются только A-Z, a-z, 0-9, _ и -.

Одно событие пришло дважды — это ошибка?

Нет, это ожидаемо: Meta, TikTok и Telegram повторяют доставку. Обрабатывайте событие один раз по dedupe_key() или event.key и отвечайте быстро.

Учитываются ли вебхуки в «Statistika» (статистике) Socialift?

Нет. Вебхуки вызывают платформы, поэтому они не записываются в использование какого-либо ключа. А хелперы SDK (socialift.webhooks) вообще не ходят в сеть — они работают только на вашем сервере.

Источники

  1. Meta: Webhooks — Getting started
  2. Meta: Page subscribed_apps
  3. Meta: Data deletion callback
  4. TikTok: Webhooks overview
  5. TikTok: Webhook signature verification
  6. Telegram Bot API: setWebhook

Связанные страницы