СтатьяАналитика

Почему одна заявка превращается в две конверсии в отчёте

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

Одна заявка разделяется на два события в цепочке аналитики, затем проходит проверку по чек-листу

Одна заявка может дважды попасть в отчёт: событие отправилось из браузера и сервера, тег сработал повторно после обновления страницы или аналитика посчитала каждое срабатывание как отдельную конверсию. До расчёта CPL разделите три вопроса: сколько событий принято, сколько уникальных лидов создано и по какому правилу площадка показывает конверсии.

Схема проверки повторных событий: браузер, правило подсчёта, идентификатор лида и учебный аудит

Сначала проверяют сбор события, затем правило подсчёта и только после этого — CPL. Цифры на схеме условные.

Почему счётчик конверсий не равен числу заявок

Событие аналитики и заявка в CRM — разные сущности. Событие сообщает, что произошло действие; CRM создаёт запись лида. Один лид может породить несколько событий, а одно событие иногда не приводит к созданию лида из-за ошибки интеграции.

В Google Analytics 4 ключевое событие можно считать один раз за событие или один раз за сеанс. Google Ads для действия-конверсии предлагает варианты «каждая» и «одна» после рекламного взаимодействия. Эти настройки отвечают на вопрос, как показать уже полученные сигналы в отчёте. Они не исправляют двойную отправку тега и не доказывают, что две записи относятся к одному лиду.

Поэтому проверка начинается не с переключателя в кабинете, а с устойчивого идентификатора результата — lead_id из CRM или формы. Для технической доставки нужен отдельный event_row_id: он помогает заметить повтор одной и той же записи.

Четыре причины двойной конверсии

ПричинаЧто видно в данныхЧто проверить
Тег сработал дваждыдва разных события с одним lead_id и почти одинаковым временемобработчик кнопки, отправку при submit и переход на страницу «спасибо»
Событие отправили браузер и серверодин lead_id, разные источники web и serverобщий идентификатор события и правило дедупликации
Пользователь повторил отправкусобытия близки по времени, но lead_id отсутствует или различаетсяCRM, форму, защиту кнопки и фактический бизнес-результат
Выбрано другое правило подсчётасырые события те же, число конверсий в колонке меняется«за событие / за сеанс» в GA4 или «каждая / одна» в Ads

Одинаковое время само по себе не доказывает дубль. Пользователь вправе отправить две разные заявки, например по двум товарам. Без lead_id такую пару лучше пометить для проверки, а не удалять автоматически.

Воспроизводимый учебный пример

В примере восемь событий generate_lead. Две строки имеют подтверждённый признак повтора: один event_row_id встретился дважды, а один lead_id пришёл в двух разных событиях. Ещё две строки относятся к одной форме и одному сеансу в пределах десяти минут, но без lead_id; скрипт отправляет вторую на ручную проверку.

Сохраните данные как lead-events.csv:

event_row_id,event_time_utc,event_name,lead_id,session_id,form_id,source
evt-001,2026-10-08T07:00:00Z,generate_lead,L-1001,s-10,callback,web
evt-002,2026-10-08T07:00:01Z,generate_lead,L-1001,s-10,callback,web
evt-003,2026-10-08T07:05:00Z,generate_lead,L-1002,s-11,brief,web
evt-003,2026-10-08T07:05:00Z,generate_lead,L-1002,s-11,brief,server
evt-004,2026-10-08T07:10:00Z,generate_lead,,s-12,demo,web
evt-005,2026-10-08T07:12:00Z,generate_lead,,s-12,demo,web
evt-006,2026-10-08T08:00:00Z,generate_lead,L-1003,s-13,quote,web
evt-007,2026-10-08T09:00:00Z,generate_lead,L-1004,s-14,callback,web

Скрипт использует только стандартную библиотеку Python. Он не меняет исходный CSV: рядом создаётся отчёт со статусом и причиной решения.

#!/usr/bin/env python3
import csv
import sys
from datetime import datetime, timedelta, timezone

def parse_time(value):
    return datetime.fromisoformat(value.replace("Z", "+00:00")).astimezone(timezone.utc)

def main(source_path, report_path):
    with open(source_path, encoding="utf-8", newline="") as source:
        rows = list(csv.DictReader(source))

    seen_event_ids = set()
    first_by_lead = {}
    recent_without_lead = {}
    result = []

    for row in sorted(rows, key=lambda item: (parse_time(item["event_time_utc"]), item["event_row_id"])):
        event_id = row["event_row_id"].strip()
        lead_id = row["lead_id"].strip()
        key = (row["session_id"].strip(), row["form_id"].strip())
        occurred_at = parse_time(row["event_time_utc"])

        if event_id in seen_event_ids:
            status = "DUPLICATE_EVENT_ID"
            reason = "тот же идентификатор события уже встречался"
        elif lead_id and lead_id in first_by_lead:
            status = "DUPLICATE_LEAD_ID"
            reason = f"lead_id уже был в строке {first_by_lead[lead_id]}"
        elif not lead_id and key in recent_without_lead and occurred_at - recent_without_lead[key] <= timedelta(minutes=10):
            status = "REVIEW_NEAR_REPEAT"
            reason = "тот же сеанс и форма в пределах 10 минут; автоматического удаления нет"
        else:
            status = "KEEP"
            reason = "подтверждённого признака дубля нет"

        seen_event_ids.add(event_id)
        if lead_id and lead_id not in first_by_lead:
            first_by_lead[lead_id] = event_id
        if not lead_id:
            recent_without_lead[key] = occurred_at
        result.append({**row, "audit_status": status, "audit_reason": reason})

    with open(report_path, "w", encoding="utf-8", newline="") as target:
        writer = csv.DictWriter(target, fieldnames=list(rows[0]) + ["audit_status", "audit_reason"])
        writer.writeheader()
        writer.writerows(result)

    counts = {}
    for row in result:
        counts[row["audit_status"]] = counts.get(row["audit_status"], 0) + 1
    print(f"raw_events={len(result)}")
    for status in ("KEEP", "REVIEW_NEAR_REPEAT", "DUPLICATE_EVENT_ID", "DUPLICATE_LEAD_ID"):
        print(f"{status.lower()}={counts.get(status, 0)}")

if __name__ == "__main__":
    if len(sys.argv) != 3:
        raise SystemExit("usage: audit_lead_events.py lead-events.csv lead-events-audit.csv")
    main(sys.argv[1], sys.argv[2])

Запуск:

python audit_lead_events.py lead-events.csv lead-events-audit.csv

Результат:

raw_events=8
keep=5
review_near_repeat=1
duplicate_event_id=1
duplicate_lead_id=1

Порог десять минут — редакционное условие учебного примера, а не правило GA4 или Google Ads. В рабочем процессе его выбирают по поведению формы и скорости обработки заявок.

Как проверить причину в интерфейсах

1. Воспроизведите одну отправку

Откройте форму в тестовом режиме, отправьте её один раз и запишите время. В GA4 DebugView проверьте поток событий с точным временем и параметрами. Если generate_lead появился дважды до передачи в рекламный кабинет, причина находится в реализации события или параллельной отправке.

2. Сверьте событие с CRM

Для каждой тестовой заявки сохраните lead_id, время создания и форму. Свяжите его с событием аналитики. Не используйте email или телефон как открытый ключ в выгрузке: передавайте внутренний непрозрачный идентификатор и соблюдайте правила работы с персональными данными.

3. Проверьте правило подсчёта

Для лидогенерации часто важна одна уникальная заявка, а для продаж — каждая покупка. Однако выбор «одна» в Google Ads ограничивает подсчёт на уровне взаимодействия, а «один раз за сеанс» в GA4 — на уровне сеанса. Ни один вариант не равен автоматически уникальному lead_id из CRM.

Изменение способа подсчёта применяется к будущим данным. Оно не пересчитывает историю, поэтому дату настройки нужно сохранить рядом с отчётом.

4. Пересчитайте CPL на трёх уровнях

В учебном примере расход — 240 BYN. По восьми сырым событиям CPL равен 30 BYN. По пяти подтверждённым уникальным лидам — 48 BYN. Если событие со статусом REVIEW_NEAR_REPEAT после проверки окажется отдельной заявкой, итог будет шесть лидов и CPL 40 BYN.

В отчёте полезно держать три колонки: raw_events, confirmed_leads, review_events. Тогда спор о CPL превращается в проверку конкретных строк. Формулу и правила включения расходов можно сверить с ответом «Что такое CPL и как рассчитать стоимость лида?».

Что исправить после аудита

Если повтор появляется уже в браузере, уберите второй обработчик, запретите повторное нажатие кнопки до ответа сервера и проверьте страницу «спасибо». Если одно действие отправляют браузер и сервер, согласуйте единый идентификатор события и дедупликацию на стороне приёма.

Если событие одно, а число конверсий отличается только между отчётами, зафиксируйте способы подсчёта и сравнивайте одинаковые колонки. Для конверсии сайта также нужен единый знаменатель — клики, сеансы или пользователи; этот выбор разобран в материале «Как считать конверсию сайта».

Не удаляйте исторические строки без следа. Храните сырой слой, статус аудита, причину исключения и версию правила. Так можно восстановить исходный отчёт и объяснить, почему CPL изменился после проверки качества данных.

Источники

Материал подготовлен редакцией WiseLab с помощью AI. Источники и фактические утверждения проверены при подготовке.

Загружаем рейтинг…

Насколько полезен материал?

Выберите оценкуСлева — меньше пользы, справа — больше

Загружаем вашу оценку…

Что думаете?

Загружаем комментарии…

СЛЕДУЮЩИЙ ШАГ

Что ещё поможет в этой задаче