В первой версии моей эталонной сборки приёма заявок повторный вебхук создал вторую сделку в CRM. Каждое отдельное выполнение умело прочитать сообщение, определить его тип и создать запись. Ошибка возникла между выполнениями: повторная доставка представляла уже полученную заявку, а запись в CRM воспринимала её как новую работу. По одному успешному прогону такой дефект не был бы заметен.
В эталонной схеме исправлением стал ключ идемпотентности на основе источника и исходной метки времени. Он сопровождает заявку при обработке и восстановлении после сбоя. Повтор должен сохранять идентичность исходного события. Если при восстановлении создаётся новая идентичность, системе не хватает сведений, чтобы отличить повтор от действительно нового обращения, даже если текст сообщений полностью совпадает.
Почему одного email недостаточно
Адрес электронной почты помогает определить контакт в пределах качества входных данных. Он не определяет каждую отдельную задачу этого человека. Один человек может прислать несколько разных запросов. Проверка дублей только по email рискует объединить новое законное обращение с предыдущей сделкой. Для бизнеса это уже потеря отдельной работы, хотя формально лишних записей в CRM не появилось.
Поэтому у примеров приёмки две задачи. Сначала повторно доставить одно исходное событие и убедиться, что второй сделки нет. Затем отправить другой запрос от того же контакта и проверить, что он сохранился отдельно. Тест только на совпадение адресов не позволяет понять, выполняются ли оба требования. Критерий нужно формулировать через заявки и события, которые действительно различает владелец процесса.
У источника и времени тоже должно быть согласованное значение. Время берётся из исходного события и сохраняется при повторе; это не момент запуска очередной попытки. Источник обозначает канал или вход, принятый в схеме. Кейс описывает такое решение, но не утверждает, что произвольная комбинация двух строк гарантированно исключает любые совпадения. В реальной интеграции идентичность события проверяется на согласованных примерах.
Сохранить ключ до записи в CRM
Сначала поток нормализует канал, контакт, сообщение, вложения, время и источник. Затем модель возвращает структуру с типом запроса, приоритетом, резюме и черновиком ответа. Неуверенные результаты идут на ручную проверку. Для записи в CRM используется исходная идентичность события независимо от того, удалось ли получить полезное обогащение от модели. Отказ квалификации не делает заявку новым событием.
Текст модели не должен определять, новая ли это заявка. Повторная квалификация может дать немного другое резюме того же сообщения. Бюджет может остаться null, если его не указали. При этом обрабатывается всё тот же запрос. Разделение исходной идентичности и сгенерированного текста позволяет проследить эту границу в логе и не связывать защиту от дублей с выбором слов моделью.
В кейсе приведён одиночный замер пути от получения заявки до структурированного результата: 7,3 секунды. Он описывает скорость конкретного прогона, но ничего не доказывает о повторной доставке. Быстрое успешное выполнение не проверяет ветку восстановления. Для неё нужен отдельный сценарий и проверка фактических записей в CRM, а не только итогового статуса выполнения в интерфейсе автоматизации.
Восстановление входит в процесс
Если CRM недоступна, заявка помещается в очередь и повторяется после восстановления. Запись в очереди должна сохранять исходные данные и ключ идемпотентности. До повтора стоит проверить, могла ли предыдущая запись уже завершиться успешно. После повтора нужно найти результат в CRM. Одной отметки об успешном выполнении недостаточно, чтобы убедиться в отсутствии второй сделки для того же события.
Сценарий с тайм-аутом требует отдельного внимания при приёмке. Отправитель мог не получить ответ, хотя принимающий сервис успел создать запись. Повтор без сохранения идентичности превратит эту неопределённость в дубль. Желаемое поведение определяется на уровне результата: исходная заявка доступна для работы, а повторная доставка не создаёт ещё одну сделку. Положение ошибки в цепочке не должно менять это требование.
Эталонная сборка различает сбой модели и недоступность CRM. Если модель не отвечает или возвращает неверный формат, выполняются три попытки с экспоненциальной паузой. Затем исходная заявка сохраняется без обогащения и помечается для разбора. При недоступности самой CRM работает очередь. Ни один из этих маршрутов не должен молча удалять исходное сообщение или прятать его от ответственного за обработку.
Оставить след для разбора
Владельцу нужны входные данные, ответ модели, решение ветки и результат записи. Эти логи связывают элемент очереди с итоговой сделкой и помогают объяснить, почему заявка попала на проверку или повтор. Секреты остаются в credentials, а права ограничиваются объектом и действиями, необходимыми интеграции. Наличие подробной истории не требует помещать токены доступа в текст выполнения.
В кейсе приёма заявок описаны исходный дубль и схема восстановления. В примере инструкции то же правило перенесено в действия оператора: проверить запись в очереди, сохранить её ключ, запустить настроенный путь восстановления и убедиться в результате непосредственно в CRM.