Повторный вебхук Telegram: как не создать второй лид в CRM

6 мин чтения

Как различать повтор события и новое обращение, сохранять входящие обновления и разбирать таймаут создания лида без слепого повтора.

Повторный вебхук Telegram: как не создать второй лид в CRM

Если одно событие Telegram доставлено дважды, CRM не должна создавать второй лид. Устойчивый идентификатор события и запись о его обработке помогают этого избежать. Но одной проверки «мы уже видели этот ID» недостаточно: если CRM создала лид, а ответ потерялся, обработчик не знает, безопасно ли повторять запрос.

Это руководство по проектированию интеграции Telegram Bot API → собственного обработчика → CRM. Оно не описывает проверенную встроенную функцию 24go.site. Предполагается, что бот уже законно получает нужные обращения, а у вас есть база данных и тестовая CRM. Эта схема не даёт права собирать чужие контактные данные или отправлять сообщения без соответствующих оснований.

Разделите идентификаторы событий, контактов и сделок

Для этих объектов нужны разные ключи и правила обработки повторов.

ОбъектЧто защищает его ключПример ключа в вашей интеграции
Входящее событиеПовторную обработку одной доставкиисточник + ваш tenant + бот + update_id
КонтактСвязь нескольких обращений с одним человекомtenant + источник + идентификатор пользователя, если он доступен
СделкаРазделение нового обращения и продолжения существующего диалогавнутренний ID обращения согласно согласованному бизнес-правилу
В Telegram Bot API update_id служит уникальным идентификатором обновления. Telegram прямо указывает, что он позволяет игнорировать повторные обновления. Дополнительные компоненты составного ключа выше предлагают изолировать записи в вашей системе; это не формат ключа Telegram.

Два разных сообщения одного человека не являются повторной доставкой одного события. Они могут относиться к одной сделке или начинать разные сделки. Это следует согласовать с владельцем процесса. Не превращайте правило «одна доставка, одна обработка» в правило «один человек, одна сделка навсегда».

Приёмник: сначала сохранить, затем подтвердить

Идемпотентность означает, что повтор операции не создаёт дополнительного эффекта. Входящие обновления нужно сохранять в постоянном хранилище, а не только в памяти процесса.

  1. Проверьте подлинность запроса и ожидаемую структуру. В setWebhook предусмотрен параметр secret_token. Если он настроен, Telegram отправляет его в заголовке X-Telegram-Bot-Api-Secret-Token. Проверьте его до постановки задания в очередь. Не записывайте значение токена в журнал.
  2. Сформируйте ключ события. Не используйте время получения запроса: при повторной доставке оно изменится.
  3. В одной транзакции сохраните событие и установите ему состояние pending. Наложите уникальное ограничение на составной ключ. Если задания хранятся в отдельной таблице, записывайте событие и задание в той же транзакции, чтобы между записями не потерять работу.
  4. После успешного commit верните успешный HTTP-ответ. Если ключ уже существует, подтвердите доставку, но не создавайте новое задание. Существующая запись pending должна оставаться доступной воркеру.
  5. Если сохранить данные в постоянном хранилище не удалось, не отвечайте успехом. Обработчик не должен сообщать о приёме события, которого он не сохранил.
В руководстве Hookdeck по идемпотентности уникальные ограничения и история обработки описаны как стратегии защиты от повторов. Важна именно гарантия ограничения базы данных: отдельные операции «проверить наличие» и «вставить» не обеспечивают такую же атомарную защиту от одновременных запросов.

Воркер: таймаут не означает, что создание не удалось

До попытки создания сохраните постоянный operation_id, идентификатор этой бизнес-операции. Он должен оставаться неизменным при повторных запусках воркера. Не допускайте, чтобы два воркера одновременно выполняли одну операцию: используйте атомарный захват задания, срок владения и процедуру восстановления зависших заданий.

Выберите следующий шаг, исходя из возможностей конкретной CRM, подтверждённых её документацией:

  • Поддерживается идемпотентное создание. Повторно используйте тот же ключ операции в течение установленного срока его действия. Не предполагайте, что любая CRM принимает заголовок Idempotency-Key.
  • Поддерживаются уникальный внешний ID и атомарный upsert. Сопоставьте операцию с этим ID. Здесь upsert означает создание или обновление по ключу. Обычный поиск с последующим созданием не обеспечивает аналогичную защиту от гонки.
  • Нет ни одного из этих механизмов. После неоднозначного таймаута переведите задание в needs_reconciliation, то есть на сверку. Проверьте CRM по внешней ссылке на операцию, журналам запросов или вручную. Если результат установить не удалось, не отправляйте запрос на создание повторно вслепую.
После подтверждённого успеха сохраните crm_id и отметьте задание как done. Если процесс завершится после создания лида, но до сохранения результата, при восстановлении нужно использовать тот же механизм идемпотентности или выполнить сверку. Одна лишь локальная таблица событий не гарантирует выполнение операции ровно один раз в удалённой CRM.

Определите, что делать с повторными и новыми сообщениями

СитуацияДействие
Тот же ключ события, запись pendingПодтвердить доставку и продолжить исходное задание
Тот же ключ события, запись doneНе повторять действие в CRM
Другой update_id, тот же контактПрименить правило продолжения или открытия сделки
Таймаут после отправки запроса на созданиеПовторить запрос с поддерживаемым постоянным ключом или выполнить сверку; не генерировать новый случайный ключ
Запоздавшее событие об изменении стадииПроверить версию или допустимость перехода, а не перезаписывать стадию безусловно
Не переносите гарантии одного поставщика на другого. Например, Stripe описывает повторную доставку событий и отсутствие гарантированного порядка доставки. Это пример того, почему нужно проверять условия доставки, а не спецификация вашей CRM или Telegram-интеграции.

Проверьте сценарии отказа до включения интеграции

На обезличенных тестовых обращениях проверьте не только штатный сценарий:

  1. Доставьте один и тот же ключ дважды, затем одновременно в двух запросах. В хранилище должно остаться одно задание, а эффект в CRM не должен дублироваться.
  2. Остановите воркер после сохранения pending. После перезапуска событие должно оставаться доступным для обработки.
  3. Смоделируйте создание записи в CRM с потерей ответа. При восстановлении нужно использовать исходный ключ или перевести задание в needs_reconciliation, а не повторять создание вслепую.
  4. Отправьте другое сообщение от того же тестового контакта. Убедитесь, что дедупликация событий не отбрасывает его.
  5. Проверьте отказ в авторизации и сбой базы данных. Поддельный запрос не должен попасть в очередь, а несохранённое событие не должно получить успешное подтверждение.
В локальной проверке SQLite к этой статье один ключ последовательно вставили дважды: в таблице осталась одна строка. Вставка другого ключа увеличила число строк до двух. Это проверка ограничения хранилища на искусственных данных, а не тест CRM, конкурентных HTTP-запросов или всей схемы восстановления.

Для эксплуатации отслеживайте возраст заданий pending, ошибки и количество заданий needs_reconciliation. Храните связь event_key → operation_id → crm_id. Не записывайте полные переписки и токены в технические журналы без необходимости. Срок хранения ключей согласуйте с фактическим периодом повторной доставки и требованиями к данным: после удаления ключа старое событие может снова выглядеть новым.

Готовность определяется не тем, что тестовый лид появился один раз. Нужно убедиться, что повторная доставка не создаёт дополнительный эффект, незавершённая работа восстанавливается, а неоднозначный ответ не приводит к дублирующей сделке.