Skip to content
TrackPilot

বাংলায় পড়ুন

GTM setup from scratch for server-side tracking

Starting with an empty Google Tag Manager? Build the data layer, web container (variables, triggers, GA4 and Meta Pixel tags) and server container (GA4 client, Conversions API Tag) with one shared Event ID.

This guide starts from an empty Google Tag Manager web container and builds everything a shop needs for server-side tracking with TrackPilot: the data layer, every variable, trigger and tag in the web container, and the server container that sends GA4 and Meta Conversions API events. Browser and server events share one Event ID, so Meta counts each action once. Plan about an hour.

Hosting the server container itself (Container Config, custom domain, loader snippet) is covered in Server-side GTM (sGTM) setup, Steps 1–5. Do those first; this page continues from there.

Before you start

  • A web container (GTM-ABC1234) and a server container in Google Tag Manager, the server container running in TrackPilot with your domain active — below we write https://data.yourshop.com.
  • The first-party loader from the TrackPilot dashboard installed on every page of your site, and the old GTM snippet removed.
  • Your Meta Pixel ID (Events Manager → your dataset) and a Conversions API access token (dataset → Settings → Generate access token).
  • Optional: a GA4 Measurement ID (G-XXXXXXXXXX) if you also want Google Analytics reports.
  • Someone who can change your shop's code or theme, for Step 1.

How the pieces fit

  1. Your shop pushes an event such as purchase into the dataLayer.
  2. The web container fires two tags on that event: a Meta Pixel tag (browser event) and a GA4 Event tag, which goes to your tagging server instead of Google.
  3. The server container's GA4 client receives it, and the Conversions API Tag sends it to Meta (server event).
  4. Both carry the same event_id, so Meta keeps one and marks the other Deduplicated.

Step 1 — Website data layer

Your shop must tell GTM what happened, in the GA4 ecommerce format. Push these four events:

EventWhenFields in ecommerce
view_itemProduct page openeditems (the product), value, currency
add_to_cartAdd to cart clickeditems (product + quantity), value, currency
add_payment_infoPayment method chosen at checkoutitems (whole cart), value, currency
purchaseThank-you / order-received page, once per ordertransaction_id, items, value (order total), currency

Each entry in items needs item_id, item_name, price and quantity. Use the same item_id as your product catalog (SKU or product ID). Numbers must be numbers, not text with a currency sign.

Always push { ecommerce: null } right before an ecommerce event. Otherwise GTM merges the new event with the previous one and old items leak into it.

window.dataLayer = window.dataLayer || [];

// Product page
dataLayer.push({ ecommerce: null });
dataLayer.push({
  event: 'view_item',
  ecommerce: {
    currency: 'BDT',
    value: 1200,
    items: [{ item_id: 'SKU-123', item_name: 'Cotton Panjabi', price: 1200, quantity: 1 }]
  }
});

// Thank-you page
dataLayer.push({ ecommerce: null });
dataLayer.push({
  event: 'purchase',
  ecommerce: {
    transaction_id: 'ORD-10045',
    value: 2450,               // order total after shipping and discounts
    currency: 'BDT',
    items: [
      { item_id: 'SKU-123', item_name: 'Cotton Panjabi', price: 1200, quantity: 2 },
      { item_id: 'SKU-456', item_name: 'Leather Belt', price: 50, quantity: 1 }
    ]
  },
  customer_data: {             // optional, improves Meta match quality
    em: '[email protected]',
    ph: '8801712345678',       // country code + number, digits only
    fn: 'Rahim'
  }
});

add_to_cart and add_payment_info have the same shape as view_item. customer_data is optional; push it only where the customer has entered it (checkout, thank-you page). Send it as plain text — the Conversions API Tag hashes it before it leaves your server. On WooCommerce, Shopify and most platforms a GA4 data-layer plugin or app produces these pushes for you; check them in GTM Preview (Step 6).

Step 2 — Web container: variables

Web container → Variables → User-Defined Variables → New. Create each one below with exactly this name — the tags in Step 4 refer to them by name.

NameVariable typeSetting
DLV - ecommerce.itemsData Layer VariableName ecommerce.items, version 2
DLV - ecommerce.items.0.item_idData Layer Variableecommerce.items.0.item_id
DLV - ecommerce.items.0.item_nameData Layer Variableecommerce.items.0.item_name
DLV - ecommerce.valueData Layer Variableecommerce.value
DLV - ecommerce.transaction_idData Layer Variableecommerce.transaction_id
DLV - customer_data.emData Layer Variablecustomer_data.em
DLV - customer_data.phData Layer Variablecustomer_data.ph
DLV - customer_data.fnData Layer Variablecustomer_data.fn
User-Provided DataUser-Provided DataManual configuration: Email = {{DLV - customer_data.em}}, Phone = {{DLV - customer_data.ph}}, First name = {{DLV - customer_data.fn}}
Const - CurrencyConstantYour shop currency, for example BDT
Const - Content TypeConstantproduct
Const - GA4 Measurement IDConstantG-XXXXXXXXXX
Const - Meta Pixel IDConstantYour Pixel ID, for example 123456789012345
Event IDEvent Id (Community Template Gallery)No settings — see below
Cookie - _fbp1st Party CookieCookie Name _fbp
Cookie - _fbc1st Party CookieCookie Name _fbc (exists only after a visit from a Meta ad)
JS - FB Contents (String)Custom JavaScriptCode below — for GA4 tags
JS - FB Contents (Array)Custom JavaScriptCode below — for Meta Pixel tags
JS - FB Content IDsCustom JavaScriptCode below — for Meta Pixel tags
JS - FB ValueCustom JavaScriptCode below — only if your shop has no ecommerce.value
JS - FB Num ItemsCustom JavaScriptCode below

Event ID: Variables → New → Variable Configuration → Discover more variable types in the Community Template Gallery → search Event Id → Add to workspace. Save it under the name Event ID. It gives every data-layer event its own ID, and every tag that fires on the same event gets the same value — that is what deduplication needs.

Custom JavaScript code (paste each into its own variable):

JS - FB Contents (String) — GA4 sends parameters as text, so the list is turned into a JSON string:

function () {
  var items = {{DLV - ecommerce.items}} || [];
  return JSON.stringify(items.map(function (i) {
    return { id: String(i.item_id), quantity: Number(i.quantity) || 1, item_price: Number(i.price) || 0 };
  }));
}

JS - FB Contents (Array) — the browser Pixel takes a real list:

function () {
  var items = {{DLV - ecommerce.items}} || [];
  return items.map(function (i) {
    return { id: String(i.item_id), quantity: Number(i.quantity) || 1, item_price: Number(i.price) || 0 };
  });
}

JS - FB Content IDs

function () {
  var items = {{DLV - ecommerce.items}} || [];
  return items.map(function (i) { return String(i.item_id); });
}

JS - FB Value

function () {
  var items = {{DLV - ecommerce.items}} || [];
  var total = 0;
  for (var k = 0; k < items.length; k++) {
    total += (Number(items[k].price) || 0) * (Number(items[k].quantity) || 1);
  }
  return total;
}

JS - FB Num Items

function () {
  var items = {{DLV - ecommerce.items}} || [];
  var n = 0;
  for (var k = 0; k < items.length; k++) {
    n += Number(items[k].quantity) || 1;
  }
  return n;
}

Wherever this guide says {{DLV - ecommerce.value}} you can use {{JS - FB Value}} instead if your shop does not push ecommerce.value. For purchase the real order total is better, because it includes shipping and discounts.

Step 3 — Web container: triggers

Web container → Triggers → New:

NameTrigger typeSetting
Initialization - All PagesInitializationAll Initialization Events
CE - view_itemCustom EventEvent name view_item, All Custom Events
CE - add_to_cartCustom EventEvent name add_to_cart
CE - add_payment_infoCustom EventEvent name add_payment_info
CE - purchaseCustom EventEvent name purchase

The GA4 tag and the Meta tag for one action must use the same trigger. Then they fire on the same data-layer event and get the same {{Event ID}}.

Step 4 — Web container: tags

4.1 Google tag (sends GA4 to your server)

Tags → New → Google tag. Name it GA4 - Google tag.

  • Tag ID: {{Const - GA4 Measurement ID}}.
  • Configuration settings → add these parameters:
ParameterValueWhy
server_container_urlhttps://data.yourshop.comSends every GA4 hit to your tagging server instead of Google
first_party_collectiontrueLets the server receive the user-provided data
x-fb-ck-fbp{{Cookie - _fbp}}The Conversions API Tag reads the Meta browser ID from this
x-fb-ck-fbc{{Cookie - _fbc}}The Meta click ID, for ad attribution
event_id{{Event ID}}Deduplicates the server PageView with the browser PageView (4.3)

Trigger: Initialization - All Pages. This tag also sends page_view.

4.2 GA4 Event tags

Tags → New → Google Analytics: GA4 Event, one per event: GA4 - view_item, GA4 - add_to_cart, GA4 - add_payment_info, GA4 - purchase.

  • Measurement ID: {{Const - GA4 Measurement ID}}.
  • Event Name: the event exactly (view_item, add_to_cart, add_payment_info, purchase).
  • More Settings → Ecommerce → tick Send Ecommerce data, Data source Data Layer. This sends the items list and transaction_id.
  • Event Parameters:
ParameterValue
event_id{{Event ID}}
value{{DLV - ecommerce.value}}
currency{{Const - Currency}}
content_ids{{DLV - ecommerce.items.0.item_id}}
content_type{{Const - Content Type}}
content_name{{DLV - ecommerce.items.0.item_name}}
num_items{{JS - FB Num Items}}
contents{{JS - FB Contents (String)}} — must be the String version
user_data{{User-Provided Data}} — arrives on the server as user_data.email_address, user_data.phone_number, user_data.address.first_name
transaction_id{{DLV - ecommerce.transaction_id}} — purchase only

Trigger: the matching CE - … trigger. Tip: build GA4 - view_item completely, then use Copy (⋮ menu) for the other three and change only the name, event name and trigger.

4.3 Meta Pixel tags

Tags → New → Discover more tag types in the Community Template Gallery → search Meta Pixel → Add to workspace. Use this official template, not the older Facebook Pixel (facebookarchive) one, which has no Event ID field.

Create five tags. In every one: Pixel ID = {{Const - Meta Pixel ID}}, Event Name = Standard + the Meta event, and More Settings → Event ID = {{Event ID}}. Leave the Meta-enabled Conversions API opt-in off — your server container already sends the server events.

Tag nameMeta eventTrigger
Meta - PageViewPageViewInitialization - All Pages
Meta - ViewContentViewContentCE - view_item
Meta - AddToCartAddToCartCE - add_to_cart
Meta - AddPaymentInfoAddPaymentInfoCE - add_payment_info
Meta - PurchasePurchaseCE - purchase

On the four ecommerce tags add Object Properties (Property Name → Property Value):

PropertyValue
content_ids{{JS - FB Content IDs}}
content_type{{Const - Content Type}}
content_name{{DLV - ecommerce.items.0.item_name}}
contents{{JS - FB Contents (Array)}} — the Array version
num_items{{JS - FB Num Items}}
value{{DLV - ecommerce.value}}
currency{{Const - Currency}}

4.4 Event map

The Conversions API Tag on the server renames GA4 events to Meta events by itself. The browser tag must use the matching Meta name:

Data layer / GA4 eventMeta eventWeb tags (same trigger)
page_view (sent by the Google tag)PageViewGA4 - Google tag + Meta - PageView
view_itemViewContentGA4 - view_item + Meta - ViewContent
add_to_cartAddToCartGA4 - add_to_cart + Meta - AddToCart
add_payment_infoAddPaymentInfoGA4 - add_payment_info + Meta - AddPaymentInfo
purchasePurchaseGA4 - purchase + Meta - Purchase

Step 5 — Server container

  1. Server container URL: server container → Admin → Container Settings → https://data.yourshop.com → Save (already done if you followed sGTM setup Step 4).
  2. Clients:
    • GA4 — present in every new server container. Keep it; it receives the events from Step 4.
    • Google Tag Manager: Web Container — Clients → New → this type → Allowed Container IDs = GTM-ABC1234. Required: without it the tagging server answers gtm.js with 400 Bad Request and your site loads no tags at all.
  3. Trigger: Triggers → New → Custom → Some Events → Client Name equals GA4. Name it GA4 client - all events. (If Client Name is not in the list, enable it under Variables → Configure.)
  4. Conversions API Tag: Templates → Tag Templates → Search Gallery → Conversions API Tag by facebookincubator → Add. Then Tags → New → Conversions API Tag:
FieldValue
Pixel IDYour Meta Pixel / dataset ID
API Access TokenThe token from Events Manager → dataset → Settings
Action SourceWebsite
Extend Meta Pixel cookies (fbp/fbc)On
Enable Event EnhancementOn
Test Event CodeOnly while testing (Step 6). Remove it before going live, or Meta treats every server event as a test.
TriggerGA4 client - all events

Optional — Google Analytics reports. With server_container_url set, GA4 hits stop going to Google directly. To keep your GA4 reports, add Tags → New → Google Analytics: GA4 (leave Measurement ID empty to use the one in the event) with the same GA4 client - all events trigger.

Step 6 — Publish and verify

  1. Publish the server container first (Submit → Publish), then the web container. The other way round, the website starts sending to a server that has no Web Container client or CAPI tag yet. The tagging server loads a new version within about a minute.
  2. Open your site and hard-reload with Ctrl+Shift+R — browsers keep the old gtm.js for a few minutes.
  3. Web Preview (web container → Preview): view a product, add to cart, check out. For each event, GA4 - … and Meta - … both show as fired, and on the Variables tab {{Event ID}} has one value.
  4. Server Preview (server container → Preview, keep that tab open, then use the site in another tab): each request is claimed by the GA4 client. In Event Data check event_id, items, value (a number), contents (a JSON list in quotes), x-fb-ck-fbp and, at checkout, user_data. Under Tags the Conversions API Tag fired, and its outgoing request to graph.facebook.com returned 200.
  5. Events Manager → Test events: put the Test Event Code into the CAPI tag, publish, and repeat the actions. Each one shows a Browser row and a Server row; the server row says Deduplicated and both share one Event ID.
  6. Meta Pixel Helper (Chrome extension): open an event — it shows the Event ID and the contents the browser sent.
  7. Remove the Test Event Code, publish the server container again. Done.

Common pitfalls

  • contents shows [object Object] on the server: a GA4 parameter that holds an array is sent as text. Use {{JS - FB Contents (String)}} in GA4 tags; keep the Array version for the Pixel.
  • Server events have only value and currency: the items list is not sent. Tick Send Ecommerce data on every GA4 Event tag.
  • No Event ID field on the Pixel tag: you are on the old "Facebook Pixel" (facebookarchive) template. Add the Meta Pixel template, or accept the update GTM offers, and set More Settings → Event ID.
  • Changes don't show after publishing: gtm.js is cached — hard-reload, or wait a few minutes.
  • The Browser row in Test events has no Event ID: that column is often empty for browser rows even when the ID is sent. Check in Meta Pixel Helper; if the server row says Deduplicated, it works.

Troubleshooting

  • No tags fire at all — the loader's gtm.js returns 400: the Web Container client is missing or the server container is not published (Step 5).
  • GA4 tag fires but nothing reaches the server — check server_container_url on the Google tag, and that your domain shows active in the TrackPilot dashboard.
  • Server event arrives, CAPI tag doesn't fire — the trigger must be Client Name equals GA4, and the GA4 client must be the one claiming the request.
  • CAPI request returns 400 — wrong Pixel ID or expired access token; the response body in Server Preview says which.
  • Events counted twice — the GA4 and Meta tags of one action use different triggers, so their Event IDs differ (Step 3).

More causes and fixes: the sGTM setup troubleshooting table. How Meta matches browser and server events: Event deduplication.


Stuck? Ask for setup help — we answer in বাংলা or English.