Вебхуки
Как подписаться на события AISAR, проверить подпись доставки и обработать ретраи.
Вебхуки — способ получать события AISAR (новое сообщение, изменение сделки, статус доставки и т.д.) в реальном времени: платформа сама отправляет POST-запрос на ваш URL при каждом событии, вместо того чтобы вы опрашивали API.
Подписка на события
Подписка создаётся как интеграция типа webhook:
curl -X POST https://api.aisar.app/v1/integrations \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "webhook",
"name": "My integration",
"status": "active",
"settings": {
"url": "https://my-app.example.com/aisar/webhook",
"selectedEventKeys": ["message.created", "deal.updated"],
"signatureHeader": "X-AISAR-Signature",
"timeoutSeconds": 30,
"retryEnabled": true,
"maxAttempts": 6
},
"secrets": {
"signingSecret": "YOUR_SIGNING_SECRET"
}
}'| Поле `settings.*` | Описание |
|---|---|
url | Куда доставлять события (http/https, проверяется SSRF-гардом) |
selectedEventKeys | Список ключей событий, на которые подписана интеграция (например message.created) |
channelFilter | "all" (по умолчанию) либо массив ID каналов — доставлять события только по этим каналам |
signatureHeader | Имя заголовка с HMAC-подписью, по умолчанию X-AISAR-Signature |
authType | none (по умолчанию), bearer или basic — добавляет заголовок Authorization в дополнение к подписи |
timeoutSeconds | Таймаут запроса доставки, 5–60 секунд, по умолчанию 30 |
retryEnabled | Включены ли повторные попытки при неуспехе, по умолчанию true |
maxAttempts | Максимум попыток доставки, 1–10, по умолчанию 6 |
Секрет для подписи передаётся отдельно, в secrets.signingSecret, и не возвращается обратно в ответах API. Полный список событий — в справочнике вебхуков (сообщения, диалоги, контакты, сделки, воронки, рассылки, каналы, шаблоны, автоматизации и другие).
Формат доставки
На ваш url приходит POST-запрос с JSON-телом и набором заголовков:
POST {webhook_url}
Content-Type: application/json
X-AISAR-Event: message.created
X-AISAR-Delivery-Id: evt_01JXXXXXXXXXXXXXXXXXXXXX_45
X-AISAR-Signature: sha256=...
User-Agent: AISAR-Webhook/1.0Все события имеют общий конверт:
{
"event": "message.created",
"event_id": "evt_01JXXXXXXXXXXXXXXXXXXXXX",
"timestamp": "2026-03-22T12:00:00.000000Z",
"company_id": 45,
"data": {
"message": {
"id": 123,
"conversation_id": 45,
"channel_id": 2,
"direction": "inbound",
"type": "text",
"content": { "text": "Hello, I need help" },
"created_at": "2026-03-22T12:00:00.000000Z"
},
"sender": {
"type": "contact",
"contact_id": 10,
"name": "John Doe",
"phone": "+77001234567"
},
"conversation": { "id": 45, "status": "active", "is_group": false },
"channel": { "id": 2, "type": "whatsapp", "name": "Main WhatsApp" }
}
}| Поле конверта | Описание |
|---|---|
event | Ключ типа события, например message.created |
event_id | Уникальный ID события (ULID), совпадает с заголовком X-AISAR-Delivery-Id |
timestamp | ISO 8601, когда событие было создано |
company_id | ID компании — источника события |
data | Тело события, структура зависит от event (см. справочник конкретного события) |
Проверка подписи
Если для интеграции задан secrets.signingSecret, каждая доставка подписывается HMAC-SHA256 по точному телу запроса (сырой JSON-байты, до какого-либо парсинга) с этим секретом. Подпись передаётся в заголовке (по умолчанию X-AISAR-Signature) в формате sha256={hex}.
Всегда сверяйте подпись по сырому телу запроса до его парсинга, и используйте сравнение, устойчивое к тайминг-атакам (hash_equals в PHP, crypto.timingSafeEqual в Node.js).
<?php
function verifyAisarSignature(string $rawBody, string $signatureHeader, string $secret): bool
{
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
return hash_equals($expected, $signatureHeader);
}
// $rawBody must be the raw request body (e.g. file_get_contents('php://input')),
// not json_decode()'d and re-encoded.
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_AISAR_SIGNATURE'] ?? '';
if (! verifyAisarSignature($rawBody, $signature, 'YOUR_SIGNING_SECRET')) {
http_response_code(401);
exit;
}
$event = json_decode($rawBody, true);
const crypto = require('crypto');
function verifyAisarSignature(rawBody, signatureHeader, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader || '');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Read the raw body with a middleware that does NOT parse JSON first
// (e.g. express.raw({ type: 'application/json' })).
app.post('/aisar/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.header('X-AISAR-Signature') || '';
if (!verifyAisarSignature(req.body, signature, process.env.AISAR_SIGNING_SECRET)) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body);
res.sendStatus(200);
});
Ретраи и таймауты
Доставка выполняется асинхронно через очередь. Запрос не следует редиректам. Таймаут запроса — значение settings.timeoutSeconds (5–60 сек, по умолчанию 30).
Доставка считается успешной при HTTP-статусе 2xx. При ошибке (не-2xx ответ, таймаут, сетевая ошибка) и включённых ретраях (retryEnabled, по умолчанию true) AISAR повторяет попытку с нарастающей задержкой, пока не будет достигнут maxAttempts (по умолчанию 6):
| Попытка | Задержка перед ней |
|---|---|
| 1 → 2 | 30 сек |
| 2 → 3 | 60 сек |
| 3 → 4 | 2 мин |
| 4 → 5 | 5 мин |
| 5 → 6 | 10 мин |
| 6 и далее | 30 мин |
Ответ 429 от вашего сервера обрабатывается отдельно: если вы передали заголовок Retry-After, AISAR подождёт не меньше указанного в нём времени. Статусы 4xx, отличные от 429, не повторяются — доставка сразу помечается неуспешной. После maxAttempts неудачных попыток доставка помечается failed окончательно. Если подряд накопится много неуспешных доставок, интеграция автоматически переводится в статус disabled.
Тестовая доставка
Прежде чем подписываться на реальные события, проверьте, что ваш эндпоинт принимает запрос и подпись верна:
curl -X POST https://api.aisar.app/v1/integrations/test-webhook \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://my-app.example.com/aisar/webhook",
"signingSecret": "YOUR_SIGNING_SECRET"
}'Событие тестовой доставки — webhook.test, тело содержит служебное сообщение "This is a test webhook delivery from AISAR." в том же конверте event/event_id/timestamp/company_id/data. Целевой URL проверяется тем же SSRF-гардом, что и боевые доставки (приватные/loopback/link-local/метаданные-адреса отклоняются).
Просмотр логов доставок
curl "https://api.aisar.app/v1/integrations/{integration}/logs?limit=50" \
-H "Authorization: Bearer YOUR_API_TOKEN"Для интеграции типа webhook возвращаются события со вложенными попытками доставки (URL, код ответа, тело ответа, наличие подписи, длительность запроса, ошибка при её наличии) — используйте это для отладки, если события до вас не доходят. limit — от 1 до 200, по умолчанию 50.