🧪 Руководство: Гибридное A/B-тестирование
В этом руководстве описан процесс внедрения A/B-теста в гибридной модели интеграции. Бэкенд сайта запрашивает вариацию кампании через API во время server-side rendering и сразу включает выбранный контент в HTML. Пользователь видит финальную версию страницы без мерцания, а взаимодействия отправляются в Gravity Field для аналитики теста.
Для новых hybrid-сценариев используйте API V2. Если у вас уже работает /ssapi/* интеграция, можно оставить API V1 и переносить тесты постепенно.
Преимущества гибридного подхода
- Отсутствие мерцания: вариация встраивается в HTML на стороне сервера.
- Высокая производительность: критичный контент загружается без ожидания клиентского JavaScript.
- Надёжность: выбор вариации выполняется на бэкенде и меньше зависит от браузерной среды.
Предварительные требования
Перед началом убедитесь, что у вас настроено следующее:
- Рабочая гибридная интеграция: бэкенд умеет взаимодействовать с API Gravity Field. Подробнее о гибридной интеграции.
- API-ключи: есть API-ключ для аутентификации запросов. Подробнее об управлении API-ключами.
- API-селектор: в кампании задан selector, по которому бэкенд будет запрашивать вариацию.
Шаг 1: Настройка кампании в интерфейсе
Создайте API-кампанию, которая будет содержать варианты для A/B-теста.
- Перейдите в раздел Campaigns -> API Campaigns и нажмите Создать кампанию.
- Выберите тип кампании Custom JSON. Этот тип позволяет вернуть данные в произвольном JSON-формате, который ваш бэкенд сможет обработать.
- Задайте API-селектор. Например:
homepage_cta_test. - Создайте как минимум две вариации.
- В каждой вариации определите JSON-объект, который описывает контент.
Вариация А:
{
"button_text": "Узнать больше",
"button_color": "#007bff"
}
Вариация Б:
{
"button_text": "Купить сейчас",
"button_color": "#28a745"
}
- Настройте распределение трафика и сохраните кампанию.
Подробнее о создании API-кампаний.
Шаг 2: Реализация на бэкенде
Логика выполняется на сервере при каждом запросе страницы, где проводится тест:
- Получить идентификаторы пользователя и сессии.
- Запросить вариацию по selector.
- Передать данные вариации в шаблонизатор.
- Сохранить идентификаторы или tracking-данные, которые понадобятся фронтенду.
- При ошибке API отрендерить fallback-контент.
Установите для API-вызова строгий timeout, например 150-200 мс. Если ответ не получен вовремя, рендерьте контент по умолчанию.
Ниже приведены два варианта API-запроса. Для новых интеграций используйте API V2: backend мапит web-cookie в user.uid и user.ses, вызывает /v2/choose, получает контент вариации и готовые tracking URL. Вариант V1 нужен для существующих интеграций через /ssapi/*.
Вариант A: API V1
V1 использует cookie _slid_server и _slsession, endpoint /ssapi/choose и decisionId для tracking.
// Псевдокод
userId = getCookie('_slid_server') ?? getCookie('_slid');
sessionId = getCookie('_slsession');
Если cookie отсутствуют, передайте null в user.slid и session.sl. Gravity Field создаст идентификаторы и вернёт их в cookies[].
curl --request POST \
--url 'https://evs-01.gravityfield.ai/ssapi/choose' \
--header 'content-type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data '{
"user": {
"slid": "USER_ID_FROM_COOKIE"
},
"session": {
"sl": "SESSION_ID_FROM_COOKIE"
},
"context": {
"page": {
"type": "HOMEPAGE",
"location": "https://your-site.com/"
},
"device": {
"userAgent": "USER_AGENT_FROM_REQUEST",
"ip": "USER_IP_FROM_REQUEST"
}
},
"selector": {
"names": [
"homepage_cta_test"
]
}
}'
Пример V1-ответа:
{
"choices": [
{
"name": "Homepage CTA Test",
"variations": [
{
"name": "Вариация Б",
"payload": {
"type": "CUSTOM_JSON",
"data": {
"custom": {
"button_text": "Купить сейчас",
"button_color": "#28a745"
}
}
}
}
],
"decisionId": "65cde...:65cdf...:65cde...:65cdf..."
}
],
"cookies": [
{
"name": "_slid_server",
"value": "NEW_OR_EXISTING_USER_ID",
"maxAge": "31556926"
},
{
"name": "_slsession",
"value": "NEW_OR_EXISTING_SESSION_ID",
"maxAge": "1800"
}
]
}
Для V1:
- извлеките данные вариации из
choices[0].variations[0].payload.data.custom; - сохраните
choices[0].decisionIdи передайте его во frontend; - если в ответе есть
cookies[], установите их черезSet-Cookie.
Вариант B: API V2
V2 использует endpoint /v2/choose, объект user с uid и ses, контекст ctx и готовые tracking URL из ответа. В web-hybrid сценарии не заводите отдельные идентификаторы для V2: мапьте существующие web-cookie в поля V2.
// Псевдокод
uid = getCookie('_slid_server') ?? getCookie('_slid');
ses = getCookie('_slsession');
Маппинг:
curl --request POST \
--url 'https://evs-01.gravityfield.ai/v2/choose' \
--header 'content-type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data '{
"sec": "YOUR_SECTION_ID",
"user": {
"uid": "VALUE_FROM__slid_server_OR__slid",
"ses": "VALUE_FROM__slsession"
},
"ctx": {
"type": "HOMEPAGE",
"data": [],
"location": "https://your-site.com/"
},
"device": {
"ua": "USER_AGENT_FROM_REQUEST",
"ip": "USER_IP_FROM_REQUEST"
},
"options": {
"isImplicitPageview": true,
"isBuildEngagementUrl": true
},
"data": [
{
"selector": "homepage_cta_test"
}
]
}'
Пример V2-ответа:
{
"user": {
"uid": "665f0a000000000000000001",
"ses": "7356efc2-6ffd-4553-bade-b9ab5d9ce141"
},
"data": [
{
"selector": "homepage_cta_test",
"payload": [
{
"variationId": "665f0d000000000000000001",
"contents": [
{
"variables": {
"button_text": "Купить сейчас",
"button_color": "#28a745",
"onVisibleImpression": {
"action": "WRIMP"
},
"onClick": {
"action": "CLICK"
}
},
"events": [
{
"type": "WRIMP",
"urls": [
"https://evs-01.gravityfield.ai/v2/engagement?..."
]
},
{
"type": "CLICK",
"urls": [
"https://evs-01.gravityfield.ai/v2/engagement?..."
]
}
]
}
]
}
]
}
]
}
Для V2:
- если в
response.userпришлиuidилиses, обновите web-cookie_slid_serverи_slsession; - извлеките данные вариации из
data[0].payload[0].contents[0].variables; - передайте во frontend нужные
content.events[].urls[], чтобы frontend мог отправить tracking; - для товарных рекомендаций дополнительно передавайте
products.slots[].events[].urls[].
Подробнее о миграции с V1 на V2.
Шаг 3: Реализация на фронтенде
Страница уже приходит в браузер с отрендеренной вариацией. Задача фронтенда - отправить tracking для реально показанного контента и действий пользователя.
Tracking в API V1
В V1 бэкенд передаёт decisionId в HTML, например в data- атрибут.
<button
id="cta-button"
style="background-color: #28a745;"
data-decision-id="65cde...:65cdf...:65cde...:65cdf..."
>
Купить сейчас
</button>
При клике отправьте engagement через JS SDK:
document.addEventListener('DOMContentLoaded', function() {
const ctaButton = document.getElementById('cta-button');
if (ctaButton) {
ctaButton.addEventListener('click', function() {
const decisionId = this.getAttribute('data-decision-id');
if (decisionId && window.SL && window.SL.ServerUtils) {
window.SL.ServerUtils.reportEngagement(decisionId, 'CLICK');
}
});
}
});
Tracking в API V2
В V2 не нужно собирать /engagement вручную. Если в /choose передан options.isBuildEngagementUrl: true, Gateway возвращает готовые URL в content.events[].urls[] и products.slots[].events[].urls[].
Передайте нужные URL в HTML или в JSON-модель страницы:
<button
id="cta-button"
style="background-color: #28a745;"
data-click-url="https://evs-01.gravityfield.ai/v2/engagement?..."
>
Купить сейчас
</button>
При действии пользователя вызовите все URL подходящего события:
function sendTrackingUrls(urls) {
urls.forEach(function(url) {
fetch(url, {
method: 'GET',
keepalive: true,
credentials: 'include'
}).catch(function() {});
});
}
document.addEventListener('DOMContentLoaded', function() {
const ctaButton = document.getElementById('cta-button');
if (ctaButton) {
ctaButton.addEventListener('click', function() {
const clickUrl = this.getAttribute('data-click-url');
if (clickUrl) {
sendTrackingUrls([clickUrl]);
}
});
}
});
Для visible impression вызывайте URL один раз, когда элемент стал видим пользователю. Для товарных рекомендаций отправляйте URL из products.slots[].events[] для конкретной карточки, а не только события контентного блока.
Шаг 4: Анализ результатов
После запуска кампании и сбора данных откройте отчёт по кампании в Gravity Field и сравните метрики вариаций.
Подробнее об анализе отчётов по кампаниям.
Полная диаграмма рабочего процесса
sequenceDiagram
participant Browser as Браузер
participant ClientBackend as Бэкенд клиента
participant GFV1 as Gravity Field API V1
participant GFV2 as Gravity Field API V2
Browser->>+ClientBackend: GET /page
Note over ClientBackend: Чтение идентификаторов пользователя и сессии
alt API V1
ClientBackend->>+GFV1: POST /ssapi/choose<br/>user.slid, session.sl, selector.names
GFV1-->>-ClientBackend: choices[], decisionId, cookies[]
Note over ClientBackend: Рендер вариации<br/>Set-Cookie при наличии cookies[]<br/>decisionId передаётся во frontend
else API V2
ClientBackend->>+GFV2: POST /v2/choose<br/>user.uid, user.ses, ctx, data[].selector
GFV2-->>-ClientBackend: data[], user, content.events[].urls[]
Note over ClientBackend: Рендер вариации<br/>обновление _slid_server/_slsession<br/>tracking URL передаются во frontend
end
ClientBackend-->>-Browser: 200 OK HTML с отрендеренной вариацией
Note over Browser: Пользователь видит страницу без мерцания
Browser->>Browser: Пользователь видит блок или кликает
alt API V1 tracking
Browser->>+GFV1: POST /ssapi/engagement или JS SDK<br/>decisionId
GFV1-->>-Browser: 200 OK
else API V2 tracking
Browser->>+GFV2: GET /v2/engagement?...<br/>URL из ответа /choose
GFV2-->>-Browser: 200 OK
endFAQ
Q: Что делать, если API /choose отвечает слишком долго?
A: Рендерьте fallback-контент и не задерживайте HTML-ответ. Для server-side запроса задайте короткий timeout.
Q: Как обрабатывать новых пользователей в V1?
A: Передавайте null в user.slid и session.sl. API V1 вернёт новые значения в cookies[], которые нужно установить пользователю.
Q: Как обрабатывать новых пользователей в V2?
A: Передайте пустой user или не передавайте uid/ses. Если Gateway вернёт response.user.uid или response.user.ses, установите их в web-cookie _slid_server и _slsession и используйте эти cookie в следующих запросах.
Q: Почему важно отслеживать взаимодействия? A: Без engagement A/B-тест не сможет корректно связать показанную вариацию с кликами, видимыми показами и итоговыми метриками.