AISARAISAR

Вебхуки

Как подписаться на события 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
authTypenone (по умолчанию), bearer или basic — добавляет заголовок Authorization в дополнение к подписи
timeoutSecondsТаймаут запроса доставки, 5–60 секунд, по умолчанию 30
retryEnabledВключены ли повторные попытки при неуспехе, по умолчанию true
maxAttemptsМаксимум попыток доставки, 1–10, по умолчанию 6

Секрет для подписи передаётся отдельно, в secrets.signingSecret, и не возвращается обратно в ответах API. Полный список событий — в справочнике вебхуков (сообщения, диалоги, контакты, сделки, воронки, рассылки, каналы, шаблоны, автоматизации и другие).

Формат доставки

На ваш url приходит POST-запрос с JSON-телом и набором заголовков:

text
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

Все события имеют общий конверт:

Пример: message.created
{
  "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
timestampISO 8601, когда событие было создано
company_idID компании — источника события
dataТело события, структура зависит от event (см. справочник конкретного события)

Проверка подписи

Если для интеграции задан secrets.signingSecret, каждая доставка подписывается HMAC-SHA256 по точному телу запроса (сырой JSON-байты, до какого-либо парсинга) с этим секретом. Подпись передаётся в заголовке (по умолчанию X-AISAR-Signature) в формате sha256={hex}.

Всегда сверяйте подпись по сырому телу запроса до его парсинга, и используйте сравнение, устойчивое к тайминг-атакам (hash_equals в PHP, crypto.timingSafeEqual в Node.js).

Проверка подписи — PHP
<?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);
Проверка подписи — Node.js
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 → 230 сек
2 → 360 сек
3 → 42 мин
4 → 55 мин
5 → 610 мин
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.