← 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 транзакции

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