Skip to navigation

Проверка вебхуков

Убедитесь, что запросы действительно от Agent Inbox.

При приёме вебхуков важно убедиться, что запрос пришёл от Agent Inbox и не был изменён. Доставка идёт через Svix с криптографической проверкой подписи.

Зачем проверять вебхуки?

Без проверки любой, кто узнает URL, может слать поддельные события:

  • Подмена данных: ложные события запускают действия в вашей системе.
  • Инциденты безопасности: вредоносные данные в потоке обработки.
  • Исчерпание ресурсов: флуд фейковыми запросами.

В продакшене проверку подписи включайте всегда.

Секрет подписи

У каждого вебхука свой секрет. Его видно в консоли Agent Inbox при создании или в ответе API:

from agentinbox import Agentinbox
client = Agentinbox()
# Детали вебхука, включая секрет
webhook = client.webhooks.get(webhook_id="ep_xxx")
# Секрет начинается с "whsec_"
signing_secret = webhook.secret
print(f"Signing secret: {signing_secret}")
Храните секрет в безопасности

Держите секрет в переменных окружения или секрет-хранилище. Не коммитьте в репозиторий и не отдавайте в клиентский код.

Заголовки проверки

В каждом запросе от Agent Inbox есть три заголовка Svix:

ЗаголовокОписание
svix-idУникальный ID сообщения. Тот же ID при ретраях.
svix-timestampUnix-время (секунды) отправки.
svix-signatureПодписи через пробел: v1,<base64> (например v1,abc123 v1,def456).

Библиотека Svix (рекомендуется)

Проще всего — официальная библиотека Svix.

import os
from dotenv import load_dotenv
from flask import Flask, request
from svix.webhooks import Webhook, WebhookVerificationError
load_dotenv()
app = Flask(__name__)
secret = os.environ["AGENTINBOX_WEBHOOK_SECRET"]
@app.route('/webhooks', methods=['POST'])
def webhook_handler():
headers = request.headers
payload = request.get_data()
try:
wh = Webhook(secret)
msg = wh.verify(payload, headers)
except WebhookVerificationError as e:
return ('', 400)
# Разбор по event_type (msg — проверенный payload)
if msg.get("event_type") == "message.received":
# обработка входящего письма...
pass
elif msg.get("event_type") == "domain.verified":
# включение функций домена...
pass
return ('', 204)
if __name__ == '__main__':
app.run(port=3000)
Нужно сырое тело

Проверка подписи требует точного тела запроса. Если используете парсер тела (express.json() и т.п.), для маршрута вебхука берите raw-тело или используйте express.raw().

Скопировать в Cursor / Claude

"""
Agent Inbox Webhook Verification — copy into Cursor/Claude.
Use Svix: pip install svix. Get secret from webhooks.get(webhook_id).secret (starts whsec_).
Headers: svix-id, svix-timestamp, svix-signature. Use request.get_data() (raw body), not request.json.
"""
from flask import Flask, request
from svix.webhooks import Webhook, WebhookVerificationError
app = Flask(__name__)
secret = "whsec_..." # from client.webhooks.get(id).secret
@app.route("/webhooks", methods=["POST"])
def handler():
try:
wh = Webhook(secret)
msg = wh.verify(request.get_data(), request.headers)
if msg.get("event_type") == "message.received":
pass # process
return "", 204
except WebhookVerificationError:
return "", 400

Локальная проверка с ngrok

Чтобы Agent Inbox достучался до локального сервера, нужен публичный URL. ngrok даёт туннель на вашу машину.

Шаг 1: Сохраните сервер

import os
from dotenv import load_dotenv
from flask import Flask, request
from svix.webhooks import Webhook, WebhookVerificationError
load_dotenv()
app = Flask(__name__)
secret = os.environ.get("AGENTINBOX_WEBHOOK_SECRET")
@app.route('/webhooks', methods=['POST'])
def webhook_handler():
headers = request.headers
payload = request.get_data()
try:
wh = Webhook(secret)
msg = wh.verify(payload, headers)
print(f"Received event: {msg}")
except WebhookVerificationError as e:
print(f"Verification failed: {e}")
return ('', 400)
if msg.get("event_type") == "message.received":
pass
elif msg.get("event_type") == "domain.verified":
pass
return ('', 204)
if __name__ == '__main__':
app.run(port=3000)

Шаг 2: Зависимости и запуск

pip install flask python-dotenv svix
python webhook_server.py

Ожидаемый вывод:

* Serving Flask app 'webhook_server'
* Debug mode: off
* Running on http://127.0.0.1:3000
Press CTRL+C to quit

Шаг 3: Запуск ngrok

ngrok http 3000

Пример вывода:

Session Status online
Account your-email@example.com (Plan: Free)
Version 3.22.1
Region United States (California) (us-cal-1)
Forwarding https://da550b82a183.ngrok.app -> http://localhost:3000

Скопируйте HTTPS forwarding URL (например https://da550b82a183.ngrok.app).

Шаг 4: URL в консоли Agent Inbox

  1. Консоль Agent Inbox
  2. Раздел Webhooks
  3. Create Webhook (или редактирование)
  4. URL с путём /webhooks, например https://da550b82a183.ngrok.app/webhooks
  5. Выберите события, сохраните
  6. Секрет подписи в .env:
AGENTINBOX_WEBHOOK_SECRET=whsec_your_secret_here

Шаг 5: Тестовое событие

Отправьте письмо на один из inbox или тест из консоли. В терминале сервера должно появиться:

127.0.0.1 - - [19/Jan/2026 16:57:20] "POST /webhooks HTTP/1.1" 204 -
Received event: {'event_type': 'message.received', ...}
Продакшен

Ngrok удобен локально; в проде разверните сервер на хостинге со стабильным HTTPS. Варианты ниже.

Продакшен

Нужен стабильный публичный HTTPS и переменные окружения. Удобный старт — Render.

Другие платформы

Также подойдут Railway, Fly.io, Heroku или любой облачный хостинг с Python и HTTPS.

Рекомендации

Локально проверку можно ослабить, в продакшене — обязательно. Скомпрометированный вебхук — серьёзный риск.

Секрет не хардкодьте:

import os
WEBHOOK_SECRET = os.environ["AGENTINBOX_WEBHOOK_SECRET"]

Устранение неполадок

  • Используйте сырое тело, не перепарсенное
  • Секрет совпадает с эндпоинтом в консоли
  • Заголовки читаются корректно (регистр не важен)
  • Время в пределах допуска (по умолчанию около 5 минут)

Прокси или фреймворк могут отрезать кастомные заголовки — проверьте настройки reverse proxy.

Для Express на маршруте вебхука используйте express.raw().