CTR1Битрикс24 с AI для роста продаж
AI
+7 (966) 863-40-80Пн–Пт 10:00–19:00
AI-анализ звонков10 сентября 2026 г.8 мин чтения

AI-webhook пришёл дважды: как обработать событие один раз

Рабочая схема обработчика webhook: сырое тело, проверка подписи, уникальный webhook-id, атомарная запись и короткий ответ серверу.

AIwebhookCRMинтеграция
AI-webhook пришёл дважды: как не создать дубль в CRM

В журнале endpoint два одинаковых webhook, а в CRM появились две задачи менеджеру. Здесь легко обвинить модель или REST, хотя причина обычно проще: обработчик считает каждую HTTP-доставку новой бизнес-операцией.

Первое, что я бы проверил, — заголовок webhook-id у обеих доставок. Если он одинаковый, перед вами один event, доставленный повторно. В CRM должна попасть одна операция. Две строки в access log при этом не ошибка.

Сначала подпись, потом бизнес-логика

Проверяйте подпись по исходному телу запроса. Если middleware уже распарсил JSON и собрал строку заново, байты могут отличаться. Для официального SDK используйте helper client.webhooks.unwrap(): он одновременно проверяет подпись и возвращает событие.

import OpenAI from 'openai';

const client = new OpenAI();

export async function webhook(req, res) {
  const rawBody = req.rawBody;
  let event;
  try {
    event = client.webhooks.unwrap(
      rawBody,
      req.headers,
      process.env.OPENAI_WEBHOOK_SECRET
    );
  } catch {
    return res.status(400).send('invalid signature');
  }

  const deliveryId = req.headers['webhook-id'];
  const accepted = await reserveOnce(deliveryId, event.type);
  if (!accepted) return res.status(200).send('duplicate ignored');

  await queue.add('openai-webhook', { deliveryId, event });
  return res.status(200).send('accepted');
}

Секрет не кладите в код и не печатайте в лог. Он нужен только серверу. Если подпись не прошла, endpoint возвращает 400 и не трогает очередь.

Почему «сначала SELECT, потом INSERT» не защищает

Два процесса могут одновременно выполнить SELECT, оба увидеть пустой результат и оба продолжить. Защиту ставят на уровне хранилища: уникальный индекс по webhook_id и атомарный INSERT. Конфликт уникальности означает «уже приняли».

СлойУникальный ключЧто он защищает
Приём webhookwebhook-idПовторную доставку одного события
Очередьjob ID из webhook-idДве одинаковые задачи worker
CRMВаш бизнес-ID результатаПовторную запись после сбоя ответа

Рабочий алгоритм

  1. Получите сырое тело и заголовки. Ограничьте размер запроса до разумного значения.
  2. Проверьте подпись официальным helper. До этого шага не вызывайте CRM.
  3. Возьмите webhook-id и зарезервируйте его атомарно.
  4. Поставьте событие в надёжную очередь и быстро верните 2xx.
  5. Worker выполнит действие и запишет бизнес-результат по стабильному ключу.
  6. После успеха отметьте событие завершённым; после временной ошибки оставьте конечный retry.

PASS и FAIL для контрольного теста

  • PASS: две доставки с одним webhook-id создают одну задачу worker.
  • PASS: изменённое тело со старой подписью получает 400 и не попадает в очередь.
  • PASS: endpoint отдаёт 2xx до завершения долгой записи в CRM.
  • FAIL: повторная доставка создаёт вторую сделку, письмо или задачу.
  • FAIL: при сбое CRM событие навсегда отмечается завершённым.

Где чаще ломается

Ответ отправляется слишком поздно. Endpoint ждёт расшифровку, письмо и запись в CRM. Поставщик не видит 2xx и повторяет доставку. Приём и обработка должны быть разными этапами.

Уникальный ключ выбран неверно. Тип response.completed одинаков у тысяч событий. Ключом служит идентификатор конкретной доставки, а бизнес-операцию дополнительно защищает ваш ID.

Статус «готово» записан до CRM. Worker упал, но повтор уже блокируется. Используйте состояния accepted, processing, completed и failed; разрешайте безопасный повтор незавершённой задачи.

В лог попадает payload целиком. Для разбора обычно хватает ID, типа, времени, кода ответа и внутреннего ID. Текст звонка и секрет подписи там не нужны.

Чек-лист перед production

  • Сырое тело доступно проверяющему SDK.
  • Signing secret хранится в переменной окружения.
  • Webhook ID имеет уникальный индекс.
  • Очередь получает стабильный job ID.
  • Ответ 2xx не зависит от скорости CRM.
  • Повтор одного payload входит в автоматический тест.

Сценарий отказа, который надо воспроизвести

Не ограничивайтесь двумя последовательными запросами из Postman. Самая неприятная гонка появляется, когда первая доставка уже зарезервировала ID, worker начал писать в CRM, а второй запрос пришёл до завершения операции. Запустите две одинаковые доставки параллельно. Один INSERT должен пройти, второй получить конфликт уникальности и завершиться 2xx без постановки второй задачи.

Затем оборвите worker после создания внешней операции, но до записи статуса completed. При повторном запуске он должен найти бизнес-результат по стабильному ключу и завершить событие, а не создать копию. Для задачи менеджеру таким ключом может быть пара «event ID + тип действия». Для записи анализа звонка — ID исходного звонка и версия анализатора.

Минимальные поля журнала событий

  • webhook_id и тип события;
  • время первой и последней доставки;
  • статус accepted, processing, completed или failed;
  • число попыток worker;
  • внутренний бизнес-ID без содержимого разговора;
  • код последней ошибки и время следующей попытки.

Поставьте срок хранения ключей длиннее возможного окна повторной доставки. Удаление записи через несколько минут возвращает старую проблему: поздний retry снова выглядит новым событием. Срок выбирайте по документации поставщика и требованиям к журналу, а очистку выполняйте отдельной контролируемой задачей.

Что делать, если не помогло

Возьмите один webhook-id и проведите его по четырём журналам: reverse proxy, endpoint, очередь, CRM. Две строки появляются впервые в одном конкретном слое. Если endpoint видит две доставки, а очередь одну, защита работает. Если очередь одна, а CRM-записей две, ищите повтор внутри worker. Для соседней проверки пригодится валидация JSON до записи в CRM и безопасный retry AI API. Мы можем разобрать AI-контур и связать события с CRM без дублей.

Хотите внедрить это в своём бизнесе?

Проведём аудит CRM, разберём процессы и покажем, что можно улучшить.

Получить аудит

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

Похожие статьи

Хотите внедрить Битрикс24 или AI в своём бизнесе?

Разберём процессы, найдём потери и покажем, что можно автоматизировать.