Keitaro и Facebook Conversions API (CAPI): настройка и скрипт
Как передавать конверсии из Keitaro в Facebook Conversions API: встроенная интеграция или скрипт, макросы S2S-постбэка, дедупликация с пикселем и проверка…

Facebook Conversions API (CAPI) передаёт конверсии в Meta с сервера, а не из браузера. В связке с Keitaro это работает так: партнёрская сеть сообщает трекеру о лиде или продаже, а Keitaro передаёт событие Lead или Purchase в пиксель через Conversions API. Сделать это можно встроенной интеграцией Keitaro или своим скриптом, который принимает S2S-постбэк. Пиксель на лендинге при этом остаётся, а одинаковый ID события не даёт Meta посчитать конверсию дважды.
Ниже оба способа, пошаговая настройка скрипта, макросы постбэка, проверка через тестовые события и частые ошибки. Скрипт можно скачать в конце статьи.
Обновлено 1 октября 2026. Мы переписали скрипт из первой версии статьи (май 2025) и нашли в старом архиве s2s серьёзные ошибки: часть данных не доходила до Meta, события уходили как пользовательские, а база с токенами открывалась по прямой ссылке. Если вы ставили архив s2s, сначала прочитайте раздел «Что было не так в версии 2025 года».
- Как работает связка Keitaro и Conversions API
- Два способа: интеграция Keitaro или свой скрипт
- Что Meta нужно от серверного события
- Шаг 1. ID пикселя и токен Conversions API
- Шаг 2. Параметры кампании в Keitaro
- Шаг 3. Пиксель на лендинге с ID события
- Шаг 4. Установка скрипта
- Шаг 5. S2S-постбэк в Keitaro
- Шаг 6. Проверка через тестовые события
- Частые ошибки
- Что было не так в версии 2025 года
- База с токенами открывалась по ссылке
- Постбэк принимал запросы от кого угодно
- Часть данных не доходила до Meta
- Имя и телефон в адресе страницы
- Что сделать, если вы ставили архив s2s
- Скачать скрипт
- Частые вопросы
- Нужен ли пиксель, если настроен Conversions API?
- Чем встроенная интеграция Keitaro отличается от скрипта?
- Можно ли отправлять в один скрипт конверсии нескольких пикселей?
- Почему конверсия не появилась в Meta?
- Нужно ли хешировать IP и User-Agent?
- Можно ли отправить конверсию, которой больше недели?
Как работает связка Keitaro и Conversions API
Одна конверсия приходит в Meta двумя путями. Первый путь браузерный: человек оставил заявку, и пиксель на странице «спасибо» отправил событие Lead. Второй путь серверный: партнёрская сеть передала лид в Keitaro, а трекер переслал его в Conversions API сам или через скрипт.
Серверный запрос не зависит от загрузки браузерного Pixel, но это не отменяет выбранные посетителем ограничения и правила использования данных. Согласуйте отправку разрешённых событий с механизмом согласий сайта. Ещё он передаёт то, чего пиксель не знает: продажу, подтверждённую через несколько дней после заявки, и выплату по ней.
Чтобы Meta не посчитала один лид дважды, оба события несут одинаковый ID. Для этого удобно взять subid клика в Keitaro: он есть и на лендинге, и в постбэке.

Два способа: интеграция Keitaro или свой скрипт
В Keitaro есть встроенная интеграция Facebook Conversion. Она доступна на тарифах Advanced, Expert, Team и Enterprise. Настраивается в разделе «Интеграции»: имя, кампании, ID пикселя и токен, а в Mapping задаётся, какой статус Keitaro каким событием уйдёт в Meta. Кампания создаётся по шаблону источника Facebook.
По документации Keitaro интеграция отправляет fbc, fbp, IP и User-Agent клика, а также страну, регион и город по IP. Почту, телефон и имя она берёт из входящего постбэка партнёрской сети. Есть два условия. Конверсия уходит, только если у клика есть fbclid. И в поле URL Parameters в Ads Manager нельзя добавлять fbclid={fbclid}: Meta дописывает fbclid к ссылке сама, а макрос мешает трекеру его записать.

Ещё одна деталь из той же документации: адрес страницы (event_source_url) интеграция берёт из referrer и передаёт в хешированном виде. Meta же ждёт его открытым адресом, этот параметр в Conversions API не хешируется.
Свой скрипт пригодится, если:
- тариф Keitaro ниже Advanced;
- пикселей много, и удобнее передавать ID пикселя прямо в постбэке, чем заводить интеграцию на каждый;
- нужен журнал с ответом Meta на каждое событие и тестовый код для отдельного постбэка;
- часть кликов приходит без fbclid, а конверсии по ним тоже нужно отправлять: скрипт сопоставит их по IP, User-Agent и external_id.
Если интеграция вам доступна и условия подходят, начните с неё. Дальше в статье разбор скрипта, но шаги про пиксель на лендинге, дедупликацию и тестовые события нужны в обоих случаях.
Что Meta нужно от серверного события
Событие для Conversions API это JSON с несколькими обязательными полями и блоком данных о посетителе. По документации Meta обязательны event_name, event_time, action_source и user_data, а для событий сайта ещё event_source_url и client_user_agent. По данным о посетителе Meta находит человека, который кликнул по рекламе. Чем больше совпадений, тем выше оценка качества сопоставления (Event Match Quality, до 10 баллов) в Events Manager и тем точнее оптимизация.

| Поле | Что передать | Откуда в Keitaro |
|---|---|---|
| event_name | Lead или Purchase, с заглавной буквы | статус конверсии |
| event_time | время конверсии в секундах Unix, не старше 7 дней | {conversion_time:U} |
| action_source | website | константа |
| event_source_url | адрес страницы с конверсией, обязательно | sub_id с адресом лендинга |
| event_id | тот же ID, что у пикселя | {subid} |
| client_user_agent | браузер посетителя, обязательно, без хеширования | {user_agent} |
| client_ip_address | IP посетителя, без хеширования | {ip} |
| fbc | fb.1.время_клика_в_мс.fbclid, без хеширования | fbclid и {date:U} |
| fbp | значение cookie _fbp, без хеширования | sub_id, если сохраняете |
| external_id, ph, em, fn, ct, country | ID, телефон, почта, имя, город, страна в SHA-256 | {subid}, sub_id из постбэка сети |
| value, currency | для Purchase явная конечная выплата ≥ 0 и код валюты из трёх латинских букв | {conversion_revenue}; валюта из currency или config.php |
Скрипт из статьи сам нормализует и хеширует данные по правилам из справочника Meta: почту и имя приводит к строчным буквам, в телефоне оставляет только цифры с кодом страны, из имени берёт первое слово, из города убирает пробелы и дефисы. Если сеть или трекер уже передают готовый SHA-256, скрипт не хеширует его второй раз.
Отдельно про fbc. Это идентификатор клика в формате fb.1.время.fbclid, где время указывается в миллисекундах, а сам fbclid нельзя менять, даже регистр букв. Если cookie _fbc вы не сохраняете, Meta разрешает собрать fbc на сервере из fbclid и времени, когда он впервые пришёл. Скрипт так и делает: берёт fbclid и время клика из Keitaro.

Если Meta отнесла ваш сайт к категории вроде «Здоровье и благополучие» (с нутрой это частая история), она может урезать параметры URL и пользовательские данные, а часть событий нижней воронки заблокировать. Подробности в справке Meta о категориях источников данных. Скрипт отправляет только стандартные события, сумму и валюту, без названий товаров.
Шаг 1. ID пикселя и токен Conversions API
В Events Manager откройте пиксель, затем «Настройки». В блоке Conversions API, в разделе ручной настройки, нажмите «Создать маркер доступа» (Generate access token). Ссылка видна пользователям с правами разработчика в бизнес-портфолио. ID пикселя написан там же, вверху страницы настроек.
Токен даёт право отправлять события в пиксель, поэтому храните его как пароль: не вставляйте в постбэки, ссылки и код лендинга. В нашем скрипте токены лежат в файле config.php на сервере, а в постбэке передаётся только ID пикселя.
Шаг 2. Параметры кампании в Keitaro
Скрипту нужны ID пикселя, fbclid клика и адрес страницы. Специальных макросов для fbclid и пикселя у S2S-постбэков Keitaro нет, поэтому их записывают в свободные поля кампании. Откройте кампанию, вкладку «Параметры», и задайте:
- sub_id_10: параметр
pixel. В ссылке объявления допишитеpixel=ID_ПИКСЕЛЯ, или впишите ID в колонку «Токен или значение», если у кампании один пиксель. - sub_id_11: параметр
fbclid. Meta сама дописывает fbclid к ссылке при клике, в объявлении его писать не нужно. Если кампания создана по шаблону Facebook и fbclid там уже записан в другой sub_id, используйте его. - sub_id_12: в колонке «Токен или значение» адрес лендинга или страницы «спасибо». Без него Meta не примет событие сайта.
Номера sub_id можно взять любые свободные, главное указать те же в адресе постбэка. Ссылка объявления в Facebook с такими параметрами выглядит так:
https://trk.site.com/AbCd12?pixel=123456789012345&utm_source=facebook&utm_campaign={{campaign.id}}&utm_content={{ad.id}}Как устроены метки и макросы Meta, мы подробно разбирали в статье про UTM-метки.
Шаг 3. Пиксель на лендинге с ID события
Пиксель на странице «спасибо» отправляет Lead с тем же ID, что потом придёт с сервера. В коде локальных лендингов Keitaro подставляет значения вместо макросов {subid} и {sub_id_10}, поэтому код пикселя выглядит так:
<script>
!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,
document,'script','https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '{sub_id_10}', { external_id: '{subid}' });
fbq('track', 'PageView');
// Lead один раз на клик: повторное открытие страницы не даст второе событие.
var leadKey = 'fb_lead_{subid}';
try {
if (!localStorage.getItem(leadKey)) {
fbq('track', 'Lead', {}, { eventID: '{subid}' });
localStorage.setItem(leadKey, '1');
}
} catch (e) {
fbq('track', 'Lead', {}, { eventID: '{subid}' });
}
</script>Главное здесь eventID. По правилам дедупликации Meta склеивает браузерное и серверное событие, если у них совпали event_name и event_id, и отбрасывает дубль, пришедший в течение 48 часов после первого. В скрипте event_id тоже равен subid. external_id пиксель хеширует сам, скрипт отправляет хеш того же subid, так что они совпадут.

Два одинаковых события из браузера Meta между собой не склеивает, поэтому в коде стоит защита от повторной отправки при обновлении страницы.
Шаг 4. Установка скрипта
Скрипту нужен PHP 7.4 или новее с расширениями curl и mbstring. Без mbstring локальная проверка вернёт HTTP 500 до обработки события: ASCII-fallback не заменяет Unicode-нормализацию. Подойдёт папка локального лендинга в Keitaro или любой ваш сервер с HTTPS.
- Скачайте архив в конце статьи и залейте папку capi на сервер.
- Скопируйте
config.sample.phpвconfig.phpи заполните ключ постбэка, пиксели с токенами и пароль журнала. - Разрешите PHP запись в папку: там появится журнал logs.
Главное в config.php выглядит так:
return [
'postback_key' => 'e3b9c1f04a7d2c6b8f15a0d93e7c4b21',
'log_password' => 'длинный-пароль-для-журнала',
'pixels' => [
'123456789012345' => 'EAAB...',
'234567890123456' => 'EAAG...',
],
'events' => [ 'lead' => 'Lead', 'sale' => 'Purchase' ],
'currency' => 'USD',
'graph_version' => 'v25.0',
'test_event_code' => '',
'default_url' => '',
];Ключ постбэка нужен, чтобы события в ваш пиксель мог отправить только Keitaro. ID пикселя виден в коде любого лендинга, и без ключа кто угодно мог бы слать через скрипт фальшивые конверсии с вашим токеном. Случайный ключ даёт команда php -r "echo bin2hex(random_bytes(16));".
В примере закреплена Graph API v25.0. Перед рабочим запуском проверьте поддержку выбранной версии и требования Conversions API по журнале изменений Graph API.
Шаг 5. S2S-постбэк в Keitaro
В кампании откройте вкладку S2S-постбэков, нажмите «Добавить», выберите метод GET и статусы lead и sale. Адрес постбэка, одной строкой:
https://trk.site.com/lander/capi/postback.php?key=КЛЮЧ&status={status}&subid={subid}&pixel={sub_id_10}&fbclid={sub_id_11}&url={sub_id_12}&click_time={date:U}&time={conversion_time:U}&ip={ip}&ua={user_agent}&payout={conversion_revenue}&country={country}&city={city}Все макросы есть в списке макросов S2S-постбэка. {date:U} отдаёт время клика в секундах Unix, {conversion_time:U} время конверсии, {conversion_revenue} доход по этой конверсии. Значения Keitaro кодирует для URL сам.

Статус lead скрипт отправит как Lead, sale как Purchase с выплатой. Остальные статусы, например rejected, он пропустит. Если у вас ещё работают постбэки от первой версии с параметрами action, clickid, user_agent, revenue и domain, скрипт их поймёт: достаточно добавить к адресу key и поменять путь.
Для Purchase параметр payout должен содержать явное конечное число не меньше нуля: payout=0 сохраняется как value=0. Пустая, нечисловая, отрицательная или бесконечная выплата возвращает локальный HTTP 422, без отправки события. Валюта берётся из currency или настройки по умолчанию и проверяется на формат трёх латинских букв; регистр нормализуется. Это проверка формата, а не полного актуального справочника валют Meta.
Телефон и почту можно добавить в постбэк, если их присылает партнёрская сеть. Входящий постбэк сети умеет обновлять sub_id_1…sub_id_30 клика и конверсии: попросите менеджера передавать телефон в свободный sub_id и добавьте в адрес &phone={sub_id_13}. Скрипт примет телефон открытым или готовым SHA-256.
Шаг 6. Проверка через тестовые события
В Events Manager откройте пиксель, вкладку «Тестовые события», раздел серверных событий, и скопируйте код вида TEST12345. Впишите его в config.php в test_event_code или добавьте к одному постбэку параметр test=TEST12345. Сделайте тестовую конверсию: событие появится во вкладке тестов.
Учтите: события с тестовым кодом Meta не отбрасывает, они учитываются как обычные. После проверки уберите test_event_code из config.php.
Журнал скрипта открывается по адресу …/capi/log.php после ввода пароля в форме. В нём видно каждое событие, ответ Meta и fbtrace_id для обращения в поддержку. Телефоны и почта в журнал не пишутся, только отметка, что они были.

Частые ошибки
| Ответ | Причина | Что делать |
|---|---|---|
| Неверный ключ постбэка (403) | нет key или он не совпадает с config.php | сверить ключ в адресе постбэка |
| Для пикселя нет токена (400) | ID пикселя не записан в config.php или в sub_id пусто | добавить пиксель, проверить параметр кампании |
| Нет user_agent (422) | в постбэке не подставился {user_agent} | проверить адрес постбэка |
| Нет url страницы (422) | sub_id с адресом пуст, default_url не задан | вписать адрес в параметры кампании |
| Старше 7 дней (422) | сеть подтвердила лид позже, чем через неделю | такие события сайта Meta не принимает |
| Ошибка 190 Invalid OAuth access token | токен неверный, отозван или от другого пикселя | создать новый токен для этого пикселя |
| Конверсии считаются дважды | eventID пикселя и event_id сервера разные | в обоих местах использовать subid |
Ошибку 190 мы получили от настоящего Graph API v25.0, отправив событие с заведомо неверным токеном: скрипт показывает её в журнале как есть. Если хотя бы одно событие в запросе старше 7 дней, Meta отклоняет весь запрос, поэтому скрипт отправляет события по одному и старые не отправляет вовсе.
Что было не так в версии 2025 года
Первая версия скрипта лежала в архиве s2s. Мы развернули его на тестовом сервере с тестовым токеном и нашли ошибки, из-за которых его нельзя оставлять в работе.
База с токенами открывалась по ссылке
Токены пикселей, имена и телефоны лидов хранились в файле db/conversions.db внутри папки скрипта. Keitaro работает на nginx, который не читает .htaccess, поэтому файл скачивался по прямому адресу. На стенде мы скачали его целиком вместе с токеном. Пароль от интерфейса по умолчанию был 5555 и передавался прямо в адресе страницы.

Постбэк принимал запросы от кого угодно
У postback.php не было ключа. Зная ID пикселя, любой мог отправить через ваш токен фальшивые конверсии. А имя байера из постбэка выводилось в журнал без экранирования: через подделанный запрос в интерфейс можно было встроить чужой скрипт.
Часть данных не доходила до Meta
IP и User-Agent отправлялись в отдельном блоке context, которого нет в Conversions API: Meta ищет их внутри user_data. События уходили как lead и sale строчными буквами, а стандартные события называются Lead и Purchase. fbc собирался из времени конверсии в секундах вместо времени клика в миллисекундах. Вместо полного адреса страницы передавался домен. Кириллические имена не приводились к строчным перед хешированием, а конверсии старше недели получали время «сейчас».
Имя и телефон в адресе страницы
Инструкция советовала передавать имя и телефон лида на страницу «спасибо» в адресе, а оттуда в трекер тоже через адрес. Так персональные данные оседали в журналах сервера, истории браузера и отчётах. Получение данных от партнёрской сети на сервере не делает открытый GET-постбэк приватным. Проверьте, какие параметры попадают в журналы трекера и веб-сервера, ограничьте доступ и не храните личные данные в query без необходимости.
Что сделать, если вы ставили архив s2s
- Удалите папку s2s с сервера целиком, вместе с db/conversions.db.
- Считайте токены из неё скомпрометированными: создайте в Events Manager новые и впишите их в config.php новой версии.
- Поставьте новую версию и поменяйте адрес постбэка: добавьте key и новый путь.
Скачать скрипт
Архив с новой версией: postback.php, log.php, lib.php, config.sample.php и инструкция README.txt. Первоначальная версия проходила 45 проверок на стенде с подставным Graph API. В патче от 31 октября 2026 исправлены обработка нулевой/невалидной выплаты Purchase и обязательная Unicode-нормализация через mbstring; в архив добавлены tests/run.php и проверка отказа без расширений. Они собирают события локально, без отправки в Meta. Доставку в настоящий API такие тесты не подтверждают. Если нужен трекер, который это всё принимает, сравнение есть в статье про трекеры для арбитража.

Частые вопросы
Нужен ли пиксель, если настроен Conversions API?
Да, Meta советует использовать оба. Пиксель передаёт события сразу и с данными браузера, сервер дополняет то, что пиксель потерял, и передаёт продажи, которых на лендинге не видно. Дубли Meta склеит по event_id.
Чем встроенная интеграция Keitaro отличается от скрипта?
Интеграция настраивается без кода, но доступна с тарифа Advanced и отправляет конверсии только по кликам с fbclid. Скрипт работает на любом тарифе, принимает ID пикселя в постбэке, ведёт журнал ответов Meta и отправляет конверсии без fbclid.
Можно ли отправлять в один скрипт конверсии нескольких пикселей?
Да. Впишите все пиксели с их токенами в config.php, а ID пикселя передавайте в постбэке. Один адрес постбэка обслужит всех байеров и все кампании.
Почему конверсия не появилась в Meta?
При HTTP 500 сначала проверьте расширения curl и mbstring. При HTTP 422 проверьте время, User-Agent, адрес страницы, а для Purchase ещё явную выплату не меньше нуля и формат валюты. Остальные ответы и ошибки ищите в log.php; нулевой payout не считается отсутствующим.
Нужно ли хешировать IP и User-Agent?
Нет. IP, User-Agent, fbc и fbp передаются как есть. В SHA-256 переводят телефон, почту, имя, город, страну, а external_id Meta рекомендует хешировать. Скрипт делает это сам.
Можно ли отправить конверсию, которой больше недели?
Нет, события сайта Meta принимает не старше 7 дней, а запрос с таким событием отклоняет целиком. Скрипт не подменяет время на текущее, а пишет такую конверсию в журнал как ошибку.



