← Back to Academia

10.30 - Server-Side GTM - Fetch API и HTTP-запросы

sendHttpRequest - главный инструмент server-side GTM. Позволяет обращаться к любому API прямо из тега: Meta CAPI, Google Ads, CRM, собственные бэкенды.

#advanced #technical #block-10 #sgtm


Навигация

10.29 - Server-Side GTM - Meta CAPI через sGTM | → 10.31 - Chrome DevTools для аналитика


Зачем HTTP-запросы из sGTM

Проблема client-side подхода

В классическом (web) GTM все запросы идут из браузера пользователя:

Браузер → facebook.com/tr (Meta Pixel) Браузер → google-analytics.com/collect (GA4) Браузер → ads.google.com (Google Ads)

Проблемы:

  • Ad-блокеры режут ~30-40% запросов (PageFair Ad Blocking Report)
  • ITP в Safari лимитирует cookies до 7 дней (3rd party) и 24 часов (1st party JS-set)
  • Каждый тег = дополнительная загрузка страницы (~50-200ms per tag)
  • Невозможно обогатить данные серверной информацией (LTV, сегмент из CRM, hashed PII)

Решение: Server-Side GTM + HTTP-запросы

Браузер → ваш домен (1st party) → sGTM сервер → {Meta CAPI, GA4, Google Ads, CRM, etc.}

Весь трафик идёт через ваш сервер. Вы контролируете:

  • Что отправляется (data quality)
  • Куда отправляется (routing)
  • Чем обогащается (server-side enrichment)
  • Что фильтруется (bot filtering, consent enforcement)

API: sendHttpRequest

Синтаксис

const sendHttpRequest = require('sendHttpRequest');
const JSON = require('JSON');
const url = 'https://api.example.com/endpoint';
const headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + data.apiKey
};
const body = JSON.stringify({
event: data.eventName,
user_id: data.userId,
timestamp: data.timestamp
});
sendHttpRequest(url, {
method: 'POST',
headers: headers,
timeout: 5000 // ms, рекомендуется 3000-5000
}, body)
.then(response => {
if (response.statusCode >= 200 && response.statusCode < 300) {
data.gtmOnSuccess();
} else {
data.gtmOnFailure();
}
})
.catch(error => {
data.gtmOnFailure();
});

Источник: Google Developers - sendHttpRequest API

Ключевые параметры

ПараметрТипОписание
urlstringEndpoint URL
options.methodstringGET, POST, PUT, PATCH, DELETE
options.headersobjectHTTP headers
options.timeoutnumberТаймаут в ms (default: 15000, рекомендуется ≤5000)
bodystringТело запроса (для POST/PUT)

Return value

Promise, который resolve'ится в объект:

{
statusCode: 200,
headers: { 'content-type': 'application/json' },
body: '{"success": true}'
}

Практические кейсы

1. Отправка конверсий в Meta CAPI

Зачем: Дублирование конверсий на сервере - обязательное требование Meta для оптимизации. Без CAPI вы теряете до 40% конверсий из-за ad-blockers и ITP.

const sendHttpRequest = require('sendHttpRequest');
const JSON = require('JSON');
const sha256Sync = require('sha256Sync');
const getTimestampMillis = require('getTimestampMillis');
const getEventData = require('getEventData');
// Данные из входящего события (через GA4 Client)
const eventName = getEventData('event_name');
const userEmail = getEventData('user_data.email_address');
const transactionId = getEventData('transaction_id');
const value = getEventData('value');
const currency = getEventData('currency') || 'KZT';
const userAgent = getEventData('user_agent');
const ipAddress = getEventData('ip_override');
const fbc = getEventData('x-ga-fbc'); // Facebook Click ID из cookie
const fbp = getEventData('x-ga-fbp'); // Facebook Browser ID из cookie
// Маппинг GA4 событий → Meta events
const eventMap = {
'page_view': 'PageView',
'add_to_cart': 'AddToCart',
'begin_checkout': 'InitiateCheckout',
'purchase': 'Purchase',
'sign_up': 'CompleteRegistration',
'generate_lead': 'Lead'
};
const metaEvent = eventMap[eventName];
if (!metaEvent) {
data.gtmOnSuccess();
return;
}
// Построение payload
const payload = {
data: [{
event_name: metaEvent,
event_time: Math.round(getTimestampMillis() / 1000),
event_id: transactionId || getEventData('event_id'), // для дедупликации
event_source_url: getEventData('page_location'),
action_source: 'website',
user_data: {
em: userEmail ? [sha256Sync(userEmail.trim().toLowerCase())] : undefined,
client_ip_address: ipAddress,
client_user_agent: userAgent,
fbc: fbc,
fbp: fbp
},
custom_data: {
value: value,
currency: currency,
order_id: transactionId
}
}]
};
const PIXEL_ID = data.pixelId; // из полей тега
const ACCESS_TOKEN = data.accessToken;
sendHttpRequest(
'https://graph.facebook.com/v19.0/' + PIXEL_ID + '/events?access_token=' + ACCESS_TOKEN,
{ method: 'POST', headers: { 'Content-Type': 'application/json' }, timeout: 5000 },
JSON.stringify(payload)
).then(response => {
if (response.statusCode === 200) {
data.gtmOnSuccess();
} else {
// Логируем ошибку для дебага
const logToConsole = require('logToConsole');
logToConsole('Meta CAPI error:', response.statusCode, response.body);
data.gtmOnFailure();
}
});

Важные нюансы:

  • event_id обязателен для дедупликации (browser pixel + CAPI отправляют одно и то же событие)
  • Email хешируется SHA256 до отправки - Meta не принимает plain text
  • fbc и fbp cookies передаются через GA4 Client автоматически, если настроен cookie forwarding

Источник: Meta - Conversions API Reference


2. Обогащение данных из CRM

Сценарий: При событии purchase вы хотите дополнить данные сегментом клиента из CRM перед отправкой в GA4 и Meta.

const sendHttpRequest = require('sendHttpRequest');
const JSON = require('JSON');
const getEventData = require('getEventData');
const setEventData = require('setEventData');
const userId = getEventData('user_id');
if (!userId) {
data.gtmOnSuccess();
return;
}
// Запрос в ваш CRM API
sendHttpRequest(
'https://api.yourcrm.com/users/' + userId + '/segment',
{
method: 'GET',
headers: {
'Authorization': 'Bearer ' + data.crmApiKey,
'Content-Type': 'application/json'
},
timeout: 3000 // Важно: timeout должен быть коротким
}
).then(response => {
if (response.statusCode === 200) {
const crmData = JSON.parse(response.body);
// Обогащаем event data - последующие теги получат эти данные
setEventData('user_segment', crmData.segment); // "high_value", "at_risk", etc.
setEventData('user_ltv', crmData.lifetime_value);
setEventData('user_cohort', crmData.acquisition_cohort);
}
data.gtmOnSuccess();
}).catch(() => {
// CRM недоступна - не блокируем, просто пропускаем обогащение
data.gtmOnSuccess();
});

Архитектурное замечание: Этот паттерн лучше реализовывать через Transformation (sGTM feature), а не через тег. Transformation выполняется до тегов, что гарантирует, что обогащённые данные доступны всем тегам.


3. Вебхук в Telegram

Сценарий: Моментальное уведомление в Telegram-канал при каждой покупке свыше определённой суммы.

const sendHttpRequest = require('sendHttpRequest');
const JSON = require('JSON');
const getEventData = require('getEventData');
const eventName = getEventData('event_name');
const value = getEventData('value');
// Фильтр: только purchase с высоким чеком
if (eventName !== 'purchase' || value < 50000) { // 50,000 KZT
data.gtmOnSuccess();
return;
}
const message = '💰 Новая покупка!\n' +
'Сумма: ' + value + ' KZT\n' +
'ID: ' + getEventData('transaction_id') + '\n' +
'Источник: ' + getEventData('traffic_source') + ' / ' + getEventData('traffic_medium');
const TELEGRAM_BOT_TOKEN = data.telegramBotToken;
const CHAT_ID = data.telegramChatId;
sendHttpRequest(
'https://api.telegram.org/bot' + TELEGRAM_BOT_TOKEN + '/sendMessage',
{ method: 'POST', headers: { 'Content-Type': 'application/json' }, timeout: 3000 },
JSON.stringify({
chat_id: CHAT_ID,
text: message,
parse_mode: 'Markdown'
})
).then(response => {
data.gtmOnSuccess();
});

4. Fetch API: sendPixel vs sendHttpRequest

sGTM предоставляет два метода для HTTP-запросов:

МетодТипBodyResponseUse case
sendPixelGET onlyНетНет (fire-and-forget)Простые пиксели, трекинг-запросы
sendHttpRequestAnyДаДа (Promise)API-вызовы, обогащение, любая логика
// sendPixel - максимально простой, для fire-and-forget
const sendPixel = require('sendPixel');
sendPixel('https://tracker.example.com/pixel?event=pageview&uid=123',
data.gtmOnSuccess, data.gtmOnFailure);
// sendHttpRequest - для всего остального
const sendHttpRequest = require('sendHttpRequest');
sendHttpRequest(url, options, body).then(callback);

Правило: Если вам не нужен response и не нужен POST - используйте sendPixel. Во всех остальных случаях - sendHttpRequest.


Паттерны и best practices

1. Параллельные запросы

Если вам нужно отправить данные в несколько endpoints - не делайте это последовательно:

// ❌ Последовательно - медленно
sendHttpRequest(metaUrl, ...).then(() => {
sendHttpRequest(googleAdsUrl, ...).then(() => {
sendHttpRequest(crmUrl, ...).then(() => {
data.gtmOnSuccess();
});
});
});
// ✅ Параллельно - быстро
const Promise = require('Promise');
Promise.all([
sendHttpRequest(metaUrl, metaOptions, metaBody),
sendHttpRequest(googleAdsUrl, googleOptions, googleBody),
sendHttpRequest(crmUrl, crmOptions, crmBody)
]).then(responses => {
// Все запросы завершены
data.gtmOnSuccess();
}).catch(error => {
data.gtmOnFailure();
});

Источник: Simo Ahava - Advanced Server-Side Tagging

2. Retry с backoff

API иногда возвращают 429 (rate limit) или 5xx. Простой retry:

function sendWithRetry(url, options, body, maxRetries, attempt) {
attempt = attempt || 1;
return sendHttpRequest(url, options, body).then(response => {
if (response.statusCode >= 500 && attempt < maxRetries) {
// Exponential backoff: 100ms, 200ms, 400ms
// Примечание: в sGTM нет setTimeout, поэтому retry мгновенный
return sendWithRetry(url, options, body, maxRetries, attempt + 1);
}
return response;
});
}
sendWithRetry(url, options, body, 3).then(response => {
if (response.statusCode >= 200 && response.statusCode < 300) {
data.gtmOnSuccess();
} else {
data.gtmOnFailure();
}
});

⚠️ Ограничение: В sGTM нет setTimeout и setInterval. Retry будет мгновенным, без паузы. Для реального backoff нужна внешняя очередь (Cloud Tasks, Pub/Sub).

3. Caching через Firestore

Если вы обогащаете данные из CRM - кешируйте результат, чтобы не делать API-вызов на каждый хит:

const Firestore = require('Firestore');
const getEventData = require('getEventData');
const userId = getEventData('user_id');
// Пробуем из кеша
Firestore.read('user_segments/' + userId, { projectId: data.gcpProjectId })
.then(doc => {
// Кеш найден - используем
setEventData('user_segment', doc.data.segment);
data.gtmOnSuccess();
})
.catch(() => {
// Кеш пуст - запрашиваем CRM и кешируем
sendHttpRequest(crmUrl, ...).then(response => {
const segment = JSON.parse(response.body).segment;
// Записываем в кеш (TTL управляется через Firestore TTL policies)
Firestore.write('user_segments/' + userId,
{ segment: segment, updated: getTimestampMillis() },
{ projectId: data.gcpProjectId, merge: true }
);
setEventData('user_segment', segment);
data.gtmOnSuccess();
});
});

Источник: Simo Ahava - Using Firestore with sGTM


Безопасность

API-ключи

Никогда не хардкодите API-ключи в шаблоне тега. Используйте:

  1. Environment variables в Cloud Run:

    const getContainerVersion = require('getContainerVersion');
    // Переменные задаются в Cloud Run → Environment Variables
  2. GTM Variables с типом «Constant» - для менее чувствительных ключей

  3. Google Secret Manager - для продакшн-уровня (через sendHttpRequest к Secret Manager API)

Permissions в Custom Templates

Если вы создаёте custom template, нужно явно запросить permissions:

// В template.tpl → permissions
__asm(
"send_http",
{
"allowedUrls": [
{ "url": "https://graph.facebook.com/", "passthrough": true },
{ "url": "https://api.yourcrm.com/", "passthrough": true }
]
}
);

Источник: Google - Custom Template Permissions


Debugging HTTP-запросов в sGTM

1. Preview Mode

В sGTM Preview Mode вы видите:

  • Входящий запрос (от web GTM)
  • Parsed event data
  • Каждый тег + его HTTP-запросы (request/response)
  • Ошибки

2. logToConsole

const logToConsole = require('logToConsole');
logToConsole('Payload:', JSON.stringify(payload));
logToConsole('Response:', response.statusCode, response.body);

Видно в Server Container → Preview → Console tab.

3. Cloud Logging (production)

Для production-мониторинга:

const logToConsole = require('logToConsole');
// В Cloud Run - logToConsole пишет в Cloud Logging (Stackdriver)
logToConsole(JSON.stringify({
severity: 'WARNING',
tag: 'meta-capi',
status: response.statusCode,
event: eventName
}));

Ограничения sGTM sandbox

ВозможностьДоступноАльтернатива
fetch() (Web API)sendHttpRequest
setTimeout / setIntervalCloud Tasks / внешняя очередь
localStorage / sessionStorageFirestore, templateDataStorage
document, window, DOMЭто server-side, DOM отсутствует
require('http') (Node.js)sendHttpRequest - единственный путь
Файловая системаCloud Storage через API
WebSocketHTTP polling

Источник: Google - Server-Side Tag Manager Sandboxed JavaScript API


Дополнительные материалы


🔧 Практика

Задание 1: Meta CAPI через sGTM

  1. Создайте server-side GTM контейнер (Stape.io free tier или Cloud Run)
  2. Настройте GA4 Client для приёма входящих хитов
  3. Создайте Custom Tag, который отправляет purchase события в Meta CAPI
  4. Проверьте через Meta Events Manager → Test Events
  5. Задокументируйте: data flow diagram + payload format

Задание 2: CRM Enrichment

  1. Создайте простой mock API (можно на Vercel / Railway) - endpoint /user/:id возвращает { segment: "high_value", ltv: 150000 }
  2. В sGTM - создайте Transformation, который вызывает этот API и добавляет user_segment в event data
  3. Убедитесь, что GA4 Tag получает обогащённые данные
  4. Добавьте Firestore caching (опционально)

Задание 3: Telegram Alert

  1. Создайте Telegram бота через @BotFather
  2. Настройте тег в sGTM, который отправляет уведомление при purchase > 100,000 KZT
  3. Включите информацию: сумма, источник трафика, ID транзакции

Связанные заметки