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
Ключевые параметры
| Параметр | Тип | Описание |
|---|---|---|
url | string | Endpoint URL |
options.method | string | GET, POST, PUT, PATCH, DELETE |
options.headers | object | HTTP headers |
options.timeout | number | Таймаут в ms (default: 15000, рекомендуется ≤5000) |
body | string | Тело запроса (для 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иfbpcookies передаются через 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-запросов:
| Метод | Тип | Body | Response | Use case |
|---|---|---|---|---|
sendPixel | GET only | Нет | Нет (fire-and-forget) | Простые пиксели, трекинг-запросы |
sendHttpRequest | Any | Да | Да (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-ключи в шаблоне тега. Используйте:
-
Environment variables в Cloud Run:
const getContainerVersion = require('getContainerVersion'); // Переменные задаются в Cloud Run → Environment Variables -
GTM Variables с типом «Constant» - для менее чувствительных ключей
-
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 / setInterval | ❌ | Cloud Tasks / внешняя очередь |
localStorage / sessionStorage | ❌ | Firestore, templateDataStorage |
document, window, DOM | ❌ | Это server-side, DOM отсутствует |
require('http') (Node.js) | ❌ | sendHttpRequest - единственный путь |
| Файловая система | ❌ | Cloud Storage через API |
| WebSocket | ❌ | HTTP polling |
Источник: Google - Server-Side Tag Manager Sandboxed JavaScript API
Дополнительные материалы
- Simo Ahava - Server-Side Tagging in GTM - лучший технический ресурс по sGTM
- Analytics Mania - Server-Side GTM Guide - пошаговые гайды
- Google - sGTM API Reference - полная документация API
- Stape.io - Blog - практические кейсы sGTM hosting
🔧 Практика
Задание 1: Meta CAPI через sGTM
- Создайте server-side GTM контейнер (Stape.io free tier или Cloud Run)
- Настройте GA4 Client для приёма входящих хитов
- Создайте Custom Tag, который отправляет
purchaseсобытия в Meta CAPI - Проверьте через Meta Events Manager → Test Events
- Задокументируйте: data flow diagram + payload format
Задание 2: CRM Enrichment
- Создайте простой mock API (можно на Vercel / Railway) - endpoint
/user/:idвозвращает{ segment: "high_value", ltv: 150000 } - В sGTM - создайте Transformation, который вызывает этот API и добавляет
user_segmentв event data - Убедитесь, что GA4 Tag получает обогащённые данные
- Добавьте Firestore caching (опционально)
Задание 3: Telegram Alert
- Создайте Telegram бота через @BotFather
- Настройте тег в sGTM, который отправляет уведомление при
purchase> 100,000 KZT - Включите информацию: сумма, источник трафика, ID транзакции