🛡️ Скрепа

Получаем updates через webhook

Результат

Вы поднимете локальный webhook endpoint, включите push-доставку, проверите секретный заголовок и вернёте gateway в polling mode без потери очереди.

Что понадобится

export SKREPA_BOT_API='http://127.0.0.1:8081'
export SKREPA_BOT_TOKEN='ваш-token-из-BotFather'
export SKREPA_WEBHOOK_SECRET="$(openssl rand -hex 32)"
set -o pipefail
printf 'Скопируйте секрет во второй терминал: %s\n' "$SKREPA_WEBHOOK_SECRET"

HTTP разрешён для loopback-адресов. Публичный endpoint должен использовать HTTPS и быть достижим из сети, где запущен gateway.

1. Запустите приёмник

Создайте webhook.py:

import hmac
import json
import os
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

SECRET = os.environ["SKREPA_WEBHOOK_SECRET"]


class Handler(BaseHTTPRequestHandler):
    def do_POST(self) -> None:
        received = self.headers.get("X-Telegram-Bot-Api-Secret-Token", "")
        if not hmac.compare_digest(received, SECRET):
            self.send_error(403)
            return

        try:
            length = int(self.headers.get("Content-Length", "0"))
            if length < 0:
                raise ValueError("negative Content-Length")
        except ValueError:
            self.send_error(400, "invalid Content-Length")
            return
        try:
            update = json.loads(self.rfile.read(length))
        except json.JSONDecodeError:
            self.send_error(400, "invalid JSON")
            return
        print(json.dumps(update, ensure_ascii=False), flush=True)
        self.send_response(204)
        self.end_headers()

    def log_message(self, _format: str, *_args: object) -> None:
        pass


ThreadingHTTPServer(("127.0.0.1", 9000), Handler).serve_forever()

Запустите его в отдельном терминале, подставив напечатанный выше секрет:

export SKREPA_WEBHOOK_SECRET='скопированный-секрет'
python3 webhook.py

2. Включите webhook mode

В исходном терминале выполните:

curl --fail-with-body -sS \
  -H 'content-type: application/json' \
  -d "$(jq -n \
    --arg secret "$SKREPA_WEBHOOK_SECRET" \
    '{url:"http://127.0.0.1:9000/update", secret_token:$secret,
      allowed_updates:["message"], max_connections:1}')" \
  "$SKREPA_BOT_API/bot${SKREPA_BOT_TOKEN}/setWebhook" | jq .

Проверьте конфигурацию:

curl --fail-with-body -sS \
  "$SKREPA_BOT_API/bot${SKREPA_BOT_TOKEN}/getWebhookInfo" | jq .

Ожидаются URL http://127.0.0.1:9000/update, max_connections: 1, allowed_updates: ["message"] и отсутствие ошибки доставки.

3. Получите update

Отправьте боту сообщение из клиента Скрепы. Терминал с webhook.py напечатает JSON-объект Update. Повторная доставка того же update_id допустима, поэтому production-обработчик должен сначала надёжно записать результат и лишь затем возвращать 2xx.

Убедитесь, что polling заблокирован во время работы webhook:

curl -sS -o /tmp/skrepa-polling-conflict.json -w 'HTTP %{http_code}\n' \
  "$SKREPA_BOT_API/bot${SKREPA_BOT_TOKEN}/getUpdates"
jq . /tmp/skrepa-polling-conflict.json
rm -f /tmp/skrepa-polling-conflict.json

Ответ должен иметь HTTP 409.

4. Вернитесь к polling

curl --fail-with-body -sS -X POST \
  "$SKREPA_BOT_API/bot${SKREPA_BOT_TOKEN}/deleteWebhook" | jq .

По умолчанию pending updates сохраняются. Параметр drop_pending_updates=true удаляет их необратимо и нужен лишь при сознательном отказе от накопленной очереди. Остановите webhook.py с помощью Ctrl+C.

Как понять, что всё получилось

Типичные ошибки

СимптомЧто проверить
last_error_message содержит connection refusedприёмник запущен, порт и адрес совпадают, URL достижим из network namespace gateway
приёмник отвечает 403секрет в процессе приёмника совпадает с secret_token из setWebhook
внешний HTTP URL отклонёниспользуйте HTTPS; незашифрованный HTTP разрешён лишь для loopback
update приходит повторнопредыдущая попытка завершилась без 2xx или ответ потерялся; дедуплицируйте по update_id
новые updates стоят в очередиголовной update получает timeout/non-2xx; исправьте endpoint и проверьте getWebhookInfo

Что гарантирует gateway