Перейти к содержимому

ГайдыРекламные платформы9 мин чтения

Дедупликация в Meta CAPI: как на самом деле работает event_id

Как Meta сопоставляет события браузерного Pixel и серверного CAPI по event_id и event_name, почему расхождения удваивают покупки и как проверить дедупликацию.

Опубликовано
Проверено
Два совпадающих идентификатора события, одна посчитанная покупка.

Daniil MaximkinРазработка продуктов и интеграций

Короткий ответ

Meta дедуплицирует событие браузерного Pixel и его серверную пару из Conversions API, если у обоих одинаковые event_id и event_name и они получены в течение 48 часов. Если id различаются, генерируются заново при каждой загрузке страницы или отсутствуют с одной стороны, Meta оставляет оба события, и это завышает число покупок и ROAS. Генерируйте один id на событие и передавайте его в опцию eventID у fbq и в серверное поле event_id.

Даниил

Главное

  • Для дедупликации нужны совпадающие event_id и event_name в обоих источниках. Если ошибиться хотя бы в одном, считаются оба события.
  • Окно дедупликации: 48 часов с момента первого события, которое Meta получила с данным event_id.
  • В Shopify обычная причина в том, что одну покупку отправляют две системы: нативный канал Facebook & Instagram плюс CAPI через GTM или приложение, либо приложение CAPI рядом с пикселем темы.
  • Проверяйте в Test Events в Events Manager: у совпавших пар там стоит метка «Deduplicated». Сводная панель проблему скрывает.
  • Используйте стабильный id, привязанный к заказу, а не новый UUID при каждой загрузке страницы, чтобы браузер и сервер совпадали при обновлениях и повторных отправках.
В этом гайде

Прежде чем что-то менять, проверьте одно событие Purchase

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

  1. Составьте список отправителей. Запишите, какие нативный канал, приложения и контейнеры GTM отправляют Purchase и в какой dataset.
  2. Сравните одну пару. Для этой же покупки зафиксируйте браузерный eventID, серверный event_id и названия событий. Отметьте каждое поле как совпадающее, различающееся или недоступное. Пропущенное значение не угадывайте.
  3. Не смешивайте наблюдение с диагнозом. Две входящие записи или разница между итогами Meta и Shopify сами по себе не доказывают, что покупку посчитали дважды. Сохраните данные событий и временное окно для дальнейшей проверки.

Если значения различаются или приложение их скрывает, подготовьте бриф на проверку Meta Purchase. Он переносит эти наблюдения в запрос, и вам не нужно ни отправлять данные клиентов, ни сначала менять настройки.

Как Meta сопоставляет браузерное событие с серверной парой?

Conversions API от Meta рассчитан на работу вместе с браузерным Pixel, а не вместо него. Они должны описывать одно и то же реальное действие с двух точек наблюдения: Pixel срабатывает в браузере покупателя, Conversions API отправляет то же событие с вашего сервера.

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

Таблица сопоставления: браузерные и серверные события связаны общим ключом; одно событие без пары помечено

Загвоздка в том, что теперь у Meta две записи об одной покупке. Дедупликация объединяет их в одну запись. Когда она работает, вы получаете охват двух источников и учитываете одну покупку. Когда нет, покупка учитывается дважды. Это искажает число конверсий, стоимость покупки и ROAS так, что неэффективные кампании выглядят успешными.

Дедупликация не работает автоматически. Это сопоставление по ключам, которые вы сами обязаны задать правильно.

По чему Meta сопоставляет события Pixel и CAPI для дедупликации?

Основной метод дедупликации в Meta сравнивает два поля в браузерном и серверном событиях:

  • event_id: уникальный идентификатор конкретного события. В Pixel он передаётся как eventID, в теле запроса Conversions API это поле event_id. Значение одно, написание разное.
  • event_name: стандартное событие, например Purchase. Параметр event в Pixel должен совпадать с event_name в Conversions API.

Если у браузерного и серверного события совпадают event_id и event_name, а Meta получает второе из них в течение 48 часов после первого, они считаются одним событием. По документации Meta, сопоставление идёт по id события и названию события вместе: одно без другого не подходит.

Вот что на самом деле происходит в каждом случае сбоя:

  • event_id различается в двух источниках. Совпадения нет. Meta записывает две конверсии. Одна покупка превращается в две.
  • event_id генерируется заново при каждой загрузке страницы. Браузер и сервер никогда не передают одно и то же значение одновременно, поэтому ничего не совпадает. Это самая частая ошибка, которую люди создают себе сами.
  • event_id отсутствует с одной стороны. Сопоставлять не с чем. Считаются оба.
  • event_name различается (Purchase и purchase). Id могут совпадать идеально, но сопоставление всё равно не сработает, потому что название входит в ключ.
  • Второе событие приходит позже чем через 48 часов. За пределами окна Meta уже зафиксировала первое, и запоздавшая пара считается отдельно.

Бизнес-последствие всегда одно: завышенные конверсии дают завышенный ROAS. Кампания с отчётным ROAS 4,0 при двойном учёте на деле работает на 2,0. Вы масштабируете её, потому что панель показывает её успешной, и направляете бюджет в кампанию, которая в лучшем случае окупается. Дубли событий добавляют шум и ведут к ошибочным решениям.

Почему Shopify дублирует события Meta Purchase?

В Shopify дублирование редко возникает из-за одного неверно настроенного тега. Его создают две системы, каждая из которых отправляет Purchase независимо от другой. Типичные сочетания:

  • Нативный канал Facebook & Instagram + CAPI через GTM или приложение. Нативный канал отправляет свои браузерные и серверные события. Добавьте второй серверный путь через GTM или приложение, и на каждый заказ будет два события Purchase без общего id.
  • Приложение CAPI + пиксель темы или Customer Events. Приложение отправляет серверное Purchase, пиксель витрины отправляет браузерное Purchase, а приложение генерирует собственные id, которых пиксель не видит.
  • Два приложения CAPI, установленных одновременно. Часто это остаток прежней настройки, который так и не удалили. Срабатывают оба, и друг с другом они не согласуются.
  • Пиксель checkout.liquid + пиксель Customer Events при недоделанном переходе на Checkout Extensibility: оба остаются на странице «Спасибо».

Что объединяет эти случаи: каждый источник может быть верен по отдельности, а их сочетание всё равно удваивает данные, потому что дедупликация работает, только когда источники передают одинаковый event_id. Два исправных источника, которые не обмениваются id, хуже одного: они создают впечатление полного охвата и незаметно считают покупку дважды. Если вы разбираетесь, какие источники работают, диагностика дедупликации Meta CAPI проведёт по инвентаризации шаг за шагом.

Дерево решений для диагностики

Что вы видите в Events ManagerВероятная причинаЧто происходитИсправление
Две строки Purchase на заказ, обе «Processed», без метки «Deduplicated»Два источника без общего idevent_id отсутствует или различается с каждой стороныИспользуйте один источник id для обоих событий; передавайте одно и то же значение в Pixel и на сервер
Пары совпадают в Test Events, но в продакшене число по-прежнему около 2× заказовevent_id генерируется заново при каждой загрузке страницыId совпадают только внутри одной загрузки, но не между браузером и серверомБерите event_id из id заказа, а не из Date.now() и не из нового UUID
Серверные события считаются отдельно; браузерные выглядят нормальноНесовпадение event_nameСервер отправляет purchase или PURCHASE, Pixel отправляет PurchaseПриведите обе стороны к Purchase (регистр важен)
Часть заказов дедуплицируется, часть нетПара уходит за 48-часовое окно, или сервер повторяет отправку с новыми idЗапоздавшая пара или пара с изменённым ключом оказывается вне сопоставленияСтабилизируйте id; убедитесь, что сервер отправляет без задержки
Общее число выглядит верным, но EMQ низкийЭто не проблема дедупликацииДедупликация в порядке; обогащение пользовательскими данными слабоеОтдельная проблема: см. Event Match Quality

Как проверить дедупликацию в Events Manager?

По главной панели это не диагностируется: она показывает итоги, а итоги скрывают двойной учёт. Откройте источник событий и перейдите в Test Events:

  1. В Events Manager выберите свой dataset (Pixel), откройте вкладку Test Events и скопируйте тестовый код.
  2. Сделайте реальную тестовую покупку (или используйте тестовый код в поле test_event_code серверного запроса, чтобы серверное событие тоже появилось).
  3. Следите за лентой входящих событий. Правильно сопоставленное событие появляется с индикатором «Deduplicated», а при раскрытии видно, что оно получено и из Browser, и из Server с одним и тем же event_id.
  4. Если вместо этого вы видите две отдельные записи Purchase, одну Browser и одну Server, без метки дедупликации, ваши id или названия не совпадают.
  5. Для исторических данных откройте отдельное событие в Overview или в подробностях события и посмотрите connection method. У корректной пары указаны и браузер, и сервер; две независимые строки на заказ выдают проблему.

Искать нужно буквальную метку «Deduplicated» на паре браузер + сервер. Если вы отправляете оба события, а метки нет, это и есть ошибка.

Набросок правильной реализации

Вся починка сводится к одной идее: сгенерируйте event_id один раз, привяжите его к заказу и передайте точно такую же строку и в Pixel, и на сервер. Вот технически корректный вариант.

Браузер (пиксель Customer Events или темы), eventID передаётся четвёртым аргументом в fbq:

// One id per purchase, derived from the order so it is identical on the server.
// NOT Date.now(), NOT a fresh crypto.randomUUID() on each load.
const eventId = `purchase_${order.id}`; // e.g. "purchase_4512890"

fbq('track', 'Purchase', {
  value: 129.00,
  currency: 'USD',
  contents: [{ id: 'SKU-123', quantity: 1 }],
  content_type: 'product'
}, { eventID: eventId });

Сервер (тело запроса Conversions API), с тем же значением в поле event_id в snake_case и тем же event_name:

{
  "data": [
    {
      "event_name": "Purchase",
      "event_time": 1690000000,
      "event_id": "purchase_4512890",
      "action_source": "website",
      "event_source_url": "https://store.example/checkout/thank-you",
      "user_data": {
        "em": ["<sha256 of lowercased, trimmed email>"],
        "fbp": "fb.1.1690000000.1234567890",
        "fbc": "fb.1.1690000000.AbCdEfGh"
      },
      "custom_data": { "value": 129.00, "currency": "USD" }
    }
  ]
}

Результат зависит от трёх условий: строки event_id совпадают байт в байт, названия событий в обоих случаях ровно Purchase (регистр важен), а id берётся из заказа, поэтому перезагрузка страницы его не изменит. Поля fbp, fbc и em улучшают качество сопоставления, но дедуплицируют не они: дедупликацию обеспечивают только event_id и event_name.

Как я проверяю это в реальных внедрениях

Я разрабатываю серверные пайплайны и основал Fixel Pixel, поэтому склоняюсь к серверным схемам. Именно поэтому дедупликацию я проверяю подсчётами, а не заявлениями.

Мой порядок проверки в каждом проекте по CAPI:

  1. Сначала инвентаризация всех источников. Прежде чем трогать код, я перечисляю все системы, которые могут отправить Purchase: нативный канал Facebook, любые приложения CAPI, GTM web, серверный GTM, пиксели темы. Дублирование почти всегда оказывается лишним источником, о котором никто не помнил, а не сломанным тегом.
  2. Параллельный запуск и Test Events вместо панели. Я делаю реальную покупку с test_event_code на сервере, чтобы появились обе половины пары, и затем убеждаюсь в буквальной метке «Deduplicated» на совпавшей паре.
  3. Сверка счётчиков с заказами. За фиксированный период я сравниваю число Purchase, о котором сообщает Meta, с реальным числом заказов Shopify. Корректная настройка даёт значение, близкое к 1:1. Число событий около 2× числа заказов выдаёт пропущенную дедупликацию, даже когда отдельные тестовые события выглядят нормально.
  4. Проверка стабильности id. Я перезагружаю страницу «Спасибо» и запускаю событие заново, чтобы убедиться, что event_id между загрузками не меняется. Если он меняется, дедупликация в продакшене сломается, хотя один чистый тест прошёл.

Сверку пропускают чаще всего, и только она выявляет периодическое дублирование.

Типичные сценарии сбоев

  • Id генерируются заново при каждой загрузке страницы. event_id строится из Date.now(), Math.random() или нового UUID при рендере. Каждая загрузка даёт новое значение, поэтому серверная пара никогда не совпадает. Берите id из заказа.
  • Компромисс: id из заказа или случайный UUID. Id из заказа стабилен и его легко воспроизвести на сервере, но его можно угадать, и он повторяется, если тот же заказ отрисован снова. Для дедупликации это обычно нормально. Случайный UUID не угадать, но он работает, только если вы генерируете его один раз и сохраняете, чтобы обе стороны читали одно и то же значение. Если сгенерировать его дважды, дублирование гарантировано. В большинстве настроек Shopify надёжнее id из заказа.
  • Расхождения в регистре и названиях. purchase и Purchase, либо пользовательское событие с одной стороны и стандартное с другой.
  • Путаница camelCase и snake_case. eventID в Pixel, event_id в серверном запросе. Если передать event_id в fbq (или eventID в серверный JSON), молча ничего не произойдёт.
  • 48-часовое окно. Пакетные или отложенные серверные отправки, которые приходят позже чем через 48 часов после браузерного события, не дедуплицируются.
  • Удалённые приложения, которые продолжают срабатывать. Удалённое приложение, у которого фрагмент пикселя или вебхук остался в теме.

Меняет ли согласие то, что показывает дедупликация?

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

Прежде чем назвать серверный Purchase из чекаута витрины без браузерной пары сбоем дедупликации, задайте ещё один вопрос: давал ли этот покупатель согласие? Если давал, браузерное событие, скорее всего, не дошло до Meta, например из-за блокировщика рекламы, закрытой вкладки до страницы подтверждения или Pixel, который там не сработал. Тогда серверное событие считается один раз (ради этого и запускают оба), и оно не дедуплицируется, потому что сопоставить его не с чем. Если покупатель отказал, вероятная причина в серверном пайплайне, который проигнорировал отказ, учтённый браузером: это ошибка согласия, а не дедупликации. Привяжите серверную отправку к тому же сигналу согласия. Речь о технической реализации, это не юридическая консультация.

Ограничения

Дедупликация сводит только одно и то же событие, описанное дважды. Она не объединяет разные события и не спасёт настройку, где два источника описывают разное (скажем, браузер срабатывает на странице «Спасибо», а сервер при списании оплаты с другим id: это два события, и посчитаны они правильно).

Она также относится только к Meta. Если привести в порядок Meta, для GA4 и Google Ads это ничего не даст: у них свои правила подсчёта. А идеальный процент дедупликации ничего не говорит о качестве сопоставления: можно безупречно дедуплицировать и при этом отправлять события, которые Meta едва может атрибутировать. Дедупликация нужна, чтобы считать один раз, а качество сопоставления решается отдельно.

Альтернативы

Если передать общий event_id нельзя, например закрытое приложение его не раскрывает, запасной вариант Meta сопоставляет по fbp или external_id плюс event_name, но только когда браузерное событие приходит первым и обе стороны последовательно передают идентификатор. Рассматривайте это как резервный способ, а не основную схему.

Более надёжный вариант: свести всё к одному серверному пути, чтобы id задавался в одном месте, а браузерный Pixel его повторял. Обычно это часть более широкой настройки серверного трекинга или Meta Conversions API, и поэтому я, как правило, предпочитаю один пайплайн с нужными проверками вместо трёх, согласованных лишь частично. Работа такого пайплайна разобрана в гайде Серверный трекинг: преимущества, ограничения и архитектура.

Нужна помощь с проверкой настройки Meta Purchase?

Используйте этот бриф, чтобы запросить проверку источников отправки и идентификаторов событий. Следующий шаг: выяснить, что можно подтвердить, затем согласовать работы по внедрению. Выбирать новое приложение CAPI заранее не нужно.

Поправьте то, что знаете, а неизвестное так и оставьте неизвестным. Не добавляйте токены доступа, данные клиентов и сырые payload. Объём работ и цена согласуются до начала платной работы.

Откроется черновик, который можно править. Перед отправкой вы проверяете сводку.

Открыть Task без передачи этого брифа ↗

Если гайд не решил вопрос25 000 ₽

Я провожу эту сверку на вашем магазине в режиме только чтения, за 25 000 ₽: письменный вывод через два рабочих дня после старта.

Вопросы

Вопросы, на которые отвечает этот гайд

Что взять как event_id: id заказа Shopify или случайный UUID?

Лучше значение, полученное из id заказа. Оно одинаково в браузере и на сервере, передавать этот идентификатор между ними не нужно, а при перезагрузке страницы «Спасибо» оно не меняется. Случайный UUID подойдёт, только если вы создаёте его один раз и передаёте обеим сторонам точно такую же строку: как только любая из сторон сгенерирует его заново, дедупликация ломается.

У моих событий статус «Processed», но никогда не «Deduplicated». Это плохо?

Да, если вы отправляете одну и ту же конверсию и из браузера, и с сервера. Метка «Deduplicated» должна стоять на совпавшей паре. Если у обеих половин только «Processed», Meta считает их отдельно: покупки учитываются дважды, а ROAS завышается.

Нужно ли, чтобы event_name тоже совпадал?

Да. Meta сопоставляет по event_id и event_name вместе. Браузерное «Purchase» и серверное «purchase» или «PURCHASE» не дедуплицируются, потому что названия различаются. Названия стандартных событий чувствительны к регистру: отправляйте «Purchase» с обеих сторон.

Сработает ли дедупликация по fbp или external_id, если я пропущу event_id?

Частично и ненадёжно. Meta может опереться на сопоставление по fbp или external_id плюс event_name, но только когда браузерное событие приходит первым и обе стороны последовательно передают один и тот же идентификатор. Это резервный способ, а не основа схемы. Отправляйте общий event_id.

Я использую приложение CAPI за $9 в месяц и нативный канал Facebook. Событие отправляется дважды?

Очень вероятно. Оба отправляют Purchase, и если они не согласуют общий event_id (а большинство связок «приложение плюс нативный канал» этого не делают), Meta получает на каждый заказ два несвязанных события Purchase. Выберите один серверный путь и убедитесь, что он использует те же id, что и браузерный Pixel.

Daniil Maximkin

Здравствуйте, я Даниил.

Я сам разбираюсь в задаче, выполняю работу и передаю результат. Все вопросы вы обсуждаете напрямую со мной. Общаюсь на русском и английском. На испанском могу переписываться с помощью переводчика.

Попробовали и всё равно не выходит?

Опишите задачу

Отвечу в течение одного рабочего дня. Первый ответ бесплатный. Или напишите напрямую: next@taskfordaniel.com

Обсудить задачу

Расскажите, что нужно разработать, исправить или подключить.

Описать задачу next@taskfordaniel.com