> For the complete documentation index, see [llms.txt](https://doc.batch.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://doc.batch.com/developer/technical-guides/how-to-guides/web/how-to-send-data-from-a-gtm-server-side-container-to-batch.md).

# How to send data from a GTM Server-Side container to Batch?

[Google Tag Manager Server-Side (sGTM)](https://developers.google.com/tag-platform/tag-manager/server-side/intro) lets you collect events on your website through a lightweight client-side snippet and forward them to your own server container before redistributing them to Batch.

This guide explains how to route those events through the [Profiles](/developer/api/cep/profiles.md) API using a Batch tag template, so that browsing behavior updates your Batch profiles in real time.

## How the flow works

{% stepper %}
{% step %}

### The website sends a hit

When a visitor triggers an event, for example `add_to_cart`, the client-side tag library builds a small HTTP request, a "hit", containing the event name, its attributes, and the visitor identifier.

The hit is sent to your server container, hosted on a first-party subdomain you own, for example `collect.yourdomain.com`.
{% endstep %}

{% step %}

### The GA4 Client receives and translates the hit

Inside the server container, a component named the **GA4 Client** listens for incoming requests, recognizes the format, and turns each hit into an event that tags can read.

The GA4 Client receives data, it does not send it.
{% endstep %}

{% step %}

### A trigger filters the events

You decide which events are forwarded to Batch.
{% endstep %}

{% step %}

### The Batch tag builds the call

It assembles the Profile API payload and sends it to Batch. The REST API Key stays on the server and is never exposed to the browser.
{% endstep %}

{% step %}

### Batch updates the profile

The Profile API returns a `202` response and the visitor profile is updated.
{% endstep %}
{% endstepper %}

{% hint style="info" %}
**About "GA4" in this guide.** The client-side tag library structures its hits in the Google Analytics 4 format, and the GA4 Client is the component that decodes that format. This does not mean data is sent to Google Analytics: the GA4 format is only used as a transport standard, because most websites already emit it. You choose where the data is routed, in this case Batch.
{% endhint %}

## Prerequisites

Before you start, make sure you have:

* A Google Tag Manager Server-Side container deployed on a first-party subdomain, for example `collect.yourdomain.com`, with a **GA4 Client** enabled to receive incoming hits.
* The Batch **REST API Key**, **Project Key**, and, for anonymous visitors, the **SDK API Key** for your project. All three are available in your Batch dashboard, with the SDK API Key under **Settings > General**.
* The Batch `custom_id` and/or `installation_id` exposed in your `dataLayer` at the time events are triggered.
* A **Batch tag template** (a `.tpl` file).

{% hint style="warning" %}
A tag template example can be provided by your Batch Solutions Engineer. Review it before use, and validate what it sends before deploying it to production: you remain responsible for the configuration running in your own container.
{% endhint %}

## Step 1: Expose the Batch identifier in the dataLayer

The Profile API requires an identifier to target the correct Batch profile. Both options below run **client-side, in the page**, not inside the sGTM container.

The server container reads these values from the incoming hit through Event Data Variables.

{% tabs %}
{% tab title="Custom User ID (authenticated)" %}
The `custom_id` comes from your own authentication system. Push it to the `dataLayer` when the user logs in, or when the page loads for an authenticated session.

Use the same stable identifier you rely on to recognize users across your systems.

Prefer the `custom_id` whenever a visitor is identified: it ensures correct profile stitching across channels.
{% endtab %}

{% tab title="Installation ID (anonymous)" %}
The Batch Web SDK generates an `installation_id` automatically. Retrieve it client-side once the SDK has initialized, then push it to the `dataLayer`:

```js
// Run client-side, in the page, after the Batch Web SDK has initialized
batchSDK(function (api) {
  api.getInstallationID().then(function (installationId) {
    window.dataLayer = window.dataLayer || [];
    window.dataLayer.push({ batch_installation_id: installationId });
  });
});
```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
Both snippets run client-side, in the browser, and depend on the page. Push the identifier as early as possible in the page lifecycle so that it is available for every event captured by the server container.
{% endhint %}

{% hint style="danger" %}
**Anonymous coverage depends on the client-side SDK.** The `installation_id` exists only if the Batch Web SDK is initialized in the browser (see [How to implement Web Push with Batch?](/getting-started/other/implementation-guides/integration-steps-web.md)). If for any reason the SDK is not loaded, no `installation_id` is generated, and anonymous events cannot be attached to a profile.

Server-side forwarding secures the collection of **identified** visitors through `custom_id`, which comes from your authentication and is independent of the SDK. It does not remove the client-side dependency for anonymous visitors.

A non-existing `installation_id` does not create a profile: the value must correspond to an installation the SDK actually registered.
{% endhint %}

## Step 2: Create the Event Data Variables

In a server container, variables that read values from an incoming hit are of type **Event Data Variable**.

{% stepper %}
{% step %}

### Create a variable

Go to **Variables** > **New**.
{% endstep %}

{% step %}

### Select the variable type

Select **Event Data Variable** as the variable type.
{% endstep %}

{% step %}

### Set the Key Path

Set the **Key Path** to the value to read:

* `user_id` for the Custom User ID
* `batch_installation_id` for the Installation ID
  {% endstep %}

{% step %}

### Save the variable

Save the variable.
{% endstep %}
{% endstepper %}

Repeat this step for every event attribute to forward to Batch. Create one Event Data Variable per attribute, with the Key Path matching the attribute name in the hit, for example `sku` or `price`.

## Step 3: Import the Batch tag template

The Batch tag template packages the Profile API call as a configurable tag, so the identity logic and attributes are set through form fields rather than a hand-written JSON body. It also preserves attribute types.

{% stepper %}
{% step %}

### Open the tag templates

In your server container, go to **Templates** > **Tag Templates** > **New**.
{% endstep %}

{% step %}

### Import the template

Open the template editor menu, represented by three dots, and select **Import**.

Select your `.tpl` file.
{% endstep %}

{% step %}

### Test and save

Run the built-in tests from the **Tests** tab to confirm the template loads correctly, then save.
{% endstep %}
{% endstepper %}

{% hint style="warning" %}
Review the template before importing it, and confirm what it sends before publishing to production. You remain responsible for the configuration that runs in your container.
{% endhint %}

## Step 4: Configure the Batch tag

Create a tag from the imported template: **Tags** > **New** > **Batch (Profile API)**.

### Configuration

* **REST API Key**: Reference a **Constant** variable of type Secret that holds your REST API Key. Never expose it in client-side code.
* **Project Key**: Your Project Key, sent in the `X-Batch-Project` header.

### Identity — Auto mode

Select **Auto** as the identifier type, then map both fields:

* **Custom User ID** → `{{ED - user_id}}`
* **Installation ID** → `{{ED - installation_id}}`
* **SDK API Key** → A Constant variable holding your SDK API Key

In Auto mode, the tag sends the `custom_id` when an identified visitor is known, and falls back to the `installation_id` for anonymous visitors.

Prefer another mode if necessary.

### Event and attributes

* **Event name** → `{{Event Name}}`
* **Event attributes** describe the action and stay attached to the event. For example: `sku` → `{{ED - sku}}`, `price` → `{{ED - price}}`
* **Profile attributes** describe the person durably. For example: `favorite_category` → `{{ED - favorite_category}}`

{% hint style="info" %}
**Event attributes vs. profile attributes.** The Profile API accepts two distinct sets in the same call:

* **Event attributes** describe the action and stay attached to the event, for example `sku` and `price` on `add_to_cart`. They are used to trigger or filter, for example "purchased with a basket value above a threshold".
* **Profile attributes** describe the person durably and persist on the profile, for example `favorite_category`, `city`, or `language`. They are used to segment and personalize.

A single event can populate both at once.
{% endhint %}

{% hint style="success" %}
**Why the tag template preserves value types.** In the Profile API, a number must be sent unquoted (`"price": 499.99`) to remain usable in numeric conditions, dates use the `date()` wrapper, and booleans are unquoted.
{% endhint %}

### Two tags for two use cases

Depending on what you send, you can split the configuration into two tags that share the same variables:

* **Events tag** (`Send an event` checked): fires on your business events, for example `add_to_cart` or `purchase`. It sends the event and can also update profile attributes in the same call.
* **Profile-only tag** (`Send an event` **unchecked**): fires on a dedicated technical event, for example `profile_update`. It updates profile attributes only, without writing an event to the profile. Use it when a value about the person changes, for example a language or a consent, outside of any tracked action.

To build the second tag, create another tag from the same template, uncheck **Send an event**, and fill in only the **Profile attributes** section. Both tags reuse the same Event Data Variables, so there is nothing to recreate.

## Step 5: Set the trigger

Configure the tag to fire on the events to forward to Batch. This selection is where your tagging plan applies: forward the events that carry value for your orchestrations, for example `purchase`, `add_to_cart`, or `view_item`, not technical hits.

{% stepper %}
{% step %}

### Open trigger settings

In the tag settings, click **Triggering**.
{% endstep %}

{% step %}

### Create or reuse a trigger

Create a **Custom Event** trigger, or reuse an existing one, matching the event names to forward.

A regular expression such as `add_to_cart|purchase` matches several events with one trigger.

If you set up two tags as described above, give each its own trigger: the business events for the events tag, and the dedicated `profile_update` event for the profile-only tag. Keep the event names disjoint so that a single hit only fires one tag.
{% endstep %}

{% step %}

### Save

Save the trigger.
{% endstep %}
{% endstepper %}

## Step 6: Test and publish

{% stepper %}
{% step %}

### Verify the request in Preview mode

Use **Preview** mode to verify the tag fires on the expected events and that the outgoing request to the Batch API returns a `202` response.
{% endstep %}

{% step %}

### Verify the profile

Verify that the profile is updated in the Batch dashboard.
{% endstep %}

{% step %}

### Publish the server container

Publish the server container.
{% endstep %}
{% endstepper %}

{% hint style="warning" %}
**Preview is not the live version.** Preview runs the draft workspace; the live website uses the last **published** version. A change that works in Preview but not on the live site is almost always an unpublished change: publish the container after every modification.
{% endhint %}

## Notes

* If both `custom_id` and `installation_id` are available, prefer the `custom_id` to ensure correct profile stitching across channels.
* When identifying by `installation_id`, the profile-level attributes `$email_address`, `$email_marketing`, `$phone_number`, and `$sms_marketing` are ignored if included.
* A `202` response with `{"code":"SUCCESS"}` means the request was accepted. A `202` with `{"code":"SUCCESS_WITH_PARTIAL_ERRORS"}` means part of the payload was rejected and details are listed in an `errors` array. Neither guarantees attachment on its own: if the identifier is empty, or the `installation_id` does not correspond to a registered installation, Batch accepts the call but attaches nothing. Always verify the profile in the dashboard.
* Common failures:
  * `AUTHENTICATION_INVALID` (401, wrong REST API Key)
  * `MALFORMED_PARAMETER` (400, for example an invalid project key)
  * `TOO_MANY_REQUESTS` (429, wait at least 5 seconds before retrying)
* For the available fields, constraints, and rate limits, see the [Profile API reference documentation](https://doc.batch.com/developer/api/cep/profiles/update).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://doc.batch.com/developer/technical-guides/how-to-guides/web/how-to-send-data-from-a-gtm-server-side-container-to-batch.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
