Дубли конверсий: как их отсекать
Дубли конверсий — это когда одно действие клиента записано в учёте два и больше раз: покупка совершена одна, а статистика и выплаты считают её за две. Для CPA-сети это не мелочь бухгалтерии, а прямые деньги: выплата вебмастеру и счёт рекламодателю назначаются за конверсию, поэтому каждый дубль — выплата, за которой не стоит реальный заказ. Ниже — откуда берутся повторы, чем платформа отличает повтор от новой продажи и как за несколько проверок убедиться, что защита работает.
Откуда дубли
Повторных сообщений об одной покупке в жизни сети много, и почти все они возникают без злого умысла.
Страница благодарности. Код учёта срабатывает на странице «спасибо» после оплаты. Клиент обновляет её вручную, возвращается назад или открывает второй раз по закладке — событие уходит повторно с тем же содержимым.
Цепочка редиректов. Платёжная система возвращает клиента на сайт через промежуточные страницы, и больше чем на одной из них может стоять код, отправляющий конверсию.
Два источника учёта. На сайте одновременно стоят браузерный пиксель и серверный постбэк. Каждый по отдельности настроен верно, но без общего ключа два сообщения об одном заказе выглядят для сети как две продажи.
Повтор после сбоя. Система рекламодателя отправила постбэк, ответ сети не дошёл — таймаут или обрыв соединения. По обычным правилам надёжной доставки запрос отправляется ещё раз: механизм доставки повторяет попытки, пока не получит подтверждение, и растягивает их во времени — секунды, минуты, часы. Покупка одна, сообщений уже два.
Повторная загрузка заказов. Интеграцию с рекламодателем перезапускают или заново синхронизируют историю заказов — и старые покупки уходят в сеть повторно, вместе с новыми. Заказы настоящие, но сообщения о них уже попадали в учёт.
Одностраничные приложения и кэш. Смена состояния страницы без перезагрузки заново поджигает те же обработчики событий; закэшированная копия страницы благодарности воспроизводит старое событие как новое.
Отдельный случай — попытка провести одну продажу как несколько. Против этого работает антифрод, но техническая защита и там та же: ключ, по которому повтор опознаётся.
У всех сценариев механика одна: один клик клиента — две записи в учёте. Запретить повторы нельзя: сеть не управляет кодом сайта рекламодателя и не может отменить ни обновление страницы, ни повторную отправку после сбоя. Вместо запрета платформа опознаёт повтор и не считает его второй раз.

Идентификатор
Платформа не видит саму покупку — она видит только сообщения о ней. Поэтому единственный способ отличить повтор от новой конверсии — ключ, который обе стороны понимают одинаково. Таких ключа два, и путать их нельзя.
Первый — идентификатор клика. Сеть выдаёт вебмастеру ссылку с меткой, запоминает клик и получает ту же метку обратно в постбэке. По ней конверсия связывается с источником: с каким переходом, каким партнёром, под какую ставку. Идентификатор клика отвечает на вопрос «откуда продажа», но не отвечает на вопрос «не та же ли это продажа».
Второй — идентификатор транзакции: номер заказа или счёта, который рекламодатель подставляет в постбэк. Он склеивает несколько сообщений об одном заказе. Пришёл второй постбэк — сеть смотрит на номер, находит уже учтённую конверсию с тем же номером и не создаёт новую запись, а возвращает статус существующей. Если номера нет, сети нечем отличить повтор: остаётся либо считать всё подряд, либо разбирать спорные записи вручную.
Идентификатор клика не заменит номер заказа. На одном клике законно помещаются две покупки: клиент перешёл по партнёрской ссылке, купил, а через месяц, внутри окна атрибуции, купил снова. При склейке по клику эта вторая настоящая продажа сольётся с первой и исчезнет из статистики и выплат. Ключ уникальности должен принадлежать покупке, а не переходу.
Практика рынка показывает обе крайности. В Google Аналитике покупки с одинаковым идентификатором транзакции склеиваются в одно событие; при этом документация отдельно просит не отправлять пустую строку вместо номера — иначе все такие покупки схлопнутся в одну, и статистика существенно занизится. Платёжные API решают ту же задачу ключом идемпотентности: в Stripe повтор запроса с тем же ключом возвращает результат первого запроса, а не выполняет операцию заново, поэтому оплата не списывается дважды.
Из этих правил следует проверка ключа из трёх свойств. Постоянный: один заказ несёт одно и то же значение при каждой отправке — номер счёта не меняется от перезагрузки страницы. Неповторяющийся: у разных заказов значения разные, иначе настоящая вторая продажа исчезнет в первой. Не пустой: пустая строка склеит не два заказа, а все. Самый надёжный источник значения — биллинг рекламодателя, а не метка времени или случайное число, сгенерированное на странице.
На практике ключ согласуют на этапе интеграции: рекламодатель добавляет номер заказа в постбэк отдельным параметром, сеть принимает сообщения в согласованном формате, а имя параметра фиксируют в документации интеграции — тогда новый разработчик с любой из сторон читает её, а не восстанавливает логику по переписке. Добавить номер в работающий поток — значит переделывать интеграцию посреди учёта; проще принять его правильно сразу.

Окно
У повтора есть и вторая координата — время. Здесь различают два окна, и это разные окна.
Окно атрибуции отвечает на вопрос, какой клик засчитать источником конверсии. Клиент мог перейти по партнёрской ссылке и купить через несколько дней — продажа всё ещё принадлежит этому переходу, пока не вышло окно. Рекламные системы к тому же позволяют выбирать, учитывать каждую конверсию после взаимодействия или только одну, — но это настройка подсчёта для отчётов, а не защита от дублей: повтор с тем же номером заказа должен гаситься независимо от неё. Сюда же ложатся холды и подтверждение конверсий: продажа сначала создаётся, потом подтверждается рекламодателем, и выплата уходит только после подтверждения.
Окно повтора — другое. Оно определяет, как долго сеть помнит ключи уже учтённых конверсий и отбрасывает повторы. Повтор приходит и через секунду после первого сообщения — двойной вызов пикселя, — и через несколько часов, когда платёжная система подтверждает отложенный платёж, и через сутки — повторная отправка после сбоя. Окно должно покрывать все эти случаи: слишком короткое пропускает медленные дубли, а слишком длинное при правильно выбранном ключе вреда не делает.
За окном защита не работает: сообщение о старом заказе, чей ключ уже списан из памяти, приходит как новая конверсия. Поэтому окно делают с запасом, а редчайшие поздние повторы ловит сверка с биллингом рекламодателя — проверка из последнего раздела. Обратная сторона запаса — размер архива ключей, но с номерами заказов в роли ключей он растёт медленно: одна короткая строка на покупку.
Ключ на это окно привязывается к заказу, а не к клиенту. Если склеивать записи по клиенту, его вторая настоящая покупка окажется «повтором», статистика партнёра просядет, а рекламодатель получит меньше продаж, чем продал. Ориентир из практики платёжных систем: ключ идемпотентности в Stripe живёт сутки — этого хватает, чтобы погасить и мгновенный, и отложенный повтор, а старые заказы не смешиваются с новыми.
Сроки обоих окон имеют смысл настраивать вместе: окно атрибуции и правила подтверждения задают, когда продажа окончательно попадает в выплаты, а окно повтора — какие сообщения об этой продаже ещё будут приходить и гаситься.
Повторный постбэк
Принимать повторы — нормально. Ошибка — не повторное сообщение, а вторая запись о нём. На рынке принят порядок, при котором повторный постбэк не создаёт новую конверсию, а возвращает статус уже существующей: создана, подтверждена, отклонена. Платформа отвечает на повтор как на сообщение об известной записи, а не как на новую заявку. Это свойство называют идемпотентностью: сколько бы раз ни пришёл один и тот же запрос, результат — одна выплата.
Повтор возвращает текущий статус конверсии, и он может не совпасть с первым ответом. Первая отправка пришла в момент создания записи и получила «создана»; повтор — после подтверждения рекламодателем, и ему уходит «подтверждена». Это нормально: оба запроса об одной записи, и решение по выплате принимает последний статус. Получив ясный ответ, система рекламодателя видит, что запись существует, и механизм доставки прекращает досылку. Если же ответ потерялся или отличается, отправляющая сторона считает попытку неудачной и пробует снова, ничего не зная о принятом решении.
Проверяется прямым экспериментом. Отправьте один и тот же постбэк дважды и посмотрите на баланс выплат: сумма не должна измениться. В логе постбэков при этом видны оба запроса — и исходный, и повтор с решением по нему. Логи важнее итогового счётчика ещё и для споров: когда рекламодатель утверждает, что продал дважды, по логу видно, какой из запросов отклонён как повтор и по какому признаку.
Повторные сообщения несут и полезный сигнал. Много одинаковых номеров от одного источника — повод посмотреть, как он настроен; одинаковые заказы с разными параметрами — профиль для антифрода. В обоих случаях решение принимается по логу, а не по догадке.
У Recca постбэки принимаются по S2S, формат совместим с конвенцией Affise, и каждый запрос остаётся в логе: повтор виден как повтор, со своим ответом, а не как новая строка в выплатах.

Проверка
Из четырёх проверок складывается уверенность, что дубли конверсий отсекаются, а не разгребаются потом. Ожидаемый результат каждой из них стоит записать цифрами: повторный постбэк создаёт 1 конверсию, вторая покупка того же клиента — 2, запрос без номера заказа — 0. Если числа получились другими, настройки разошлись с планом.
Первая — двойной постбэк. Один заказ, два одинаковых запроса подряд: конверсия должна создаться одна, баланс выплат не меняется, в логе — два запроса и одно решение.
Вторая — два разных заказа от одного клиента. Обе конверсии должны встать в учёт: проверка заодно показывает, что ключ привязан к заказу, а не к покупателю.
Третья — пустой ключ. Запрос без номера заказа не должен молча попасть в статистику и тем более не должен схлопывать с собой другие записи.
Четвёртая — сверка с биллингом рекламодателя. За один день число подтверждённых конверсий и число реальных заказов совпадают. Расхождение — повод разбирать лог, а не поправлять счётчик: за ним стоит либо пропущенный повтор, либо пережатая склейка.
Проверки стоит повторять после каждой перемены в интеграции: новая страница благодарности, обновление платёжной системы, правка формата постбэка — каждая из них способна вернуть уже решённые дубли.
Обе ошибки стоят денег, но по-разному. Пропущенный дубль — выплата без продажи: рекламодатель увидит её в счёте, оспорит, и сеть вернёт деньги уже после спора. Пережатая склейка тише: конверсии партнёров исчезают из статистики без заявлений с той или другой стороны, и доверие к цифрам сети уходит само. Поэтому проверяют в обе стороны — и на лишние записи, и на потерянные.
Если вы собираете свою CPA-сеть и хотите, чтобы повторы отсекались на входе, а не в спорах с рекламодателями, — оставьте заявку на подключение тарифа «CPA-сеть».