# TD Web SDK Integration

This guide explains how to install the **TD Web SDK** (`td-web-sdk`) on your website and configure it for In-Browser Messaging. Complete this setup before creating campaigns in Engage Studio.

Who should read this
This page is for **web developers** who are adding the SDK to a website. Marketers who are creating campaigns can skip to [Create a Popup Campaign](/products/marketing-cloud/engage-studio/experiences/create-a-popup-campaign) or [Create an Inline Campaign](/products/marketing-cloud/engage-studio/experiences/create-an-inline-campaign) once setup is complete.

## Step 1 — Install the SDK

Choose the installation method that matches your project.

CDN (Recommended)
Add the async loader snippet to the `<head>` of every page. This is the fastest way to get started and works with any tech stack.

```html
<head>
  <!-- TD Web SDK async loader -->
  <script type="text/javascript">
  !function(t,e){if(void 0===e[t]){e[t]=function(){e[t].clients.push(this),this._init=[Array.prototype.slice.call(arguments)]},e[t].clients=[];for(var r=function(t){return function(){return this["_"+t]=this["_"+t]||[],this["_"+t].push(Array.prototype.slice.call(arguments)),this}},s=["set","trackEvent","trackPageview","trackClicks","addRecord","setSignedMode","setAnonymousMode","blockEvents","unblockEvents","fetchGlobalID","fetchServerCookie","fetchUserSegments","fetchPersonalization","resetUUID","collectTags","ready"],c=0;c<s.length;c++){var o=s[c];e[t].prototype[o]=r(o)}var n=document.createElement("script");n.type="text/javascript",n.async=!0,n.src="https://cdn.treasuredata.com/sdk/web/1.0/td-sdk.min.js";var i=document.getElementsByTagName("script")[0];i.parentNode.insertBefore(n,i)}}("Treasure",this);
  </script>
</head>
```

CDN versioning
The path `/sdk/web/1.0/` pins to the minor version. Patch releases are automatically reflected without requiring a snippet update.

npm
Install via npm for projects using a module bundler (webpack, Vite, Rollup, etc.).

```bash
npm install td-web-sdk
```

```typescript
// TypeScript / ES modules
import Treasure from 'td-web-sdk'

// CommonJS
const Treasure = require('td-web-sdk').default
```

## Step 2 — Initialize the SDK

Create one `Treasure` instance and keep a reference to it. Call initialization as early as possible — before any `trackEvent` calls.

```javascript
const td = new Treasure({
  writeKey: 'YOUR_WRITE_KEY',   // Write-only API key from your TD account
  database: 'YOUR_DATABASE',    // Target database name
  host: 'YOUR_REGION_HOST',     // See region list below
})
```

### Enable In-Browser Messaging (Personalization)

To receive popup or inline messages from Engage Studio, add the `personalization` option. When configured, `trackEvent` calls are routed through the **RT Personalization API** instead of the standard ingest endpoint — the SDK fetches the campaign payload and renders the message automatically.

```javascript
const td = new Treasure({
  writeKey: 'YOUR_WRITE_KEY',
  database: 'YOUR_DATABASE',
  host: 'YOUR_REGION_HOST',
  personalization: {
    endpoint: 'YOUR_P13N_ENDPOINT',  // Region-dependent. Ask your CSM.
    token: 'YOUR_WP13N_TOKEN',       // WP13n-Token from Audience Studio
  },
})
```

Personalization replaces ingest
When `personalization` is configured, **events are not written to your TD database** — they are sent to the RT Personalization API instead. If you need both ingest and personalization, contact your Customer Success Manager for guidance on the dual-path pattern.

### Configuration Reference

| **Option**  | **Required**  | **Description**  |
|  --- | --- | --- |
| `writeKey` | ✅ | Write-only API key. Found in your TD account settings. |
| `database` | ✅ | Target database name where events are stored. |
| `host`
 | —
 | Data ingestion endpoint. Select the value for your region:
| Region | Host |
|  --- | --- |
| US | `us01.records.in.treasuredata.com` |
| Tokyo | `ap01.records.in.treasuredata.com` |
| AP02 | `ap02.records.in.treasuredata.com` |
| EU01 | `eu01.records.in.treasuredata.com` |

 |
| `personalization.endpoint` | ✅ (if using In-Browser Messaging) | RT Personalization API endpoint. Region-dependent. |
| `personalization.token` | ✅ (if using In-Browser Messaging) | WP13n-Token from the Personalization settings in Audience Studio. |
| `startInSignedMode` | — | Set to `true` to collect PII (client ID, IP) from the first event. Default: `false` (anonymous mode). |
| `logging` | — | Enable SDK console logging for debugging. Default: `true` in development. |


## Step 3 — Track Page Views

Fire a page view event on every page load or SPA route change using `trackEvent` with `event_name: 'page_view'`. This is required for RT Personalization to evaluate entry criteria and deliver messages.

Static HTML / MPA
Call `trackEvent` on every page load. Because the page reloads on each navigation, one call per page is sufficient.

```html
<script>
  // After SDK initialization
  td.trackEvent('events', { event_name: 'page_view' })
</script>
```

React SPA
For client-side route changes, call `trackEvent` in a `useEffect` that watches the route — but **skip the first render** to avoid a double page view on initial load.

```jsx
// components/Layout.jsx
import { useEffect, useRef } from 'react'
import { useLocation } from 'react-router-dom'

export default function Layout({ children }) {
  const location = useLocation()
  const firstRender = useRef(true)

  useEffect(() => {
    if (firstRender.current) {
      firstRender.current = false
      return // Skip: initial view already tracked on load
    }
    window.td?.trackEvent('events', { event_name: 'page_view' })
  }, [location.pathname, location.search])

  return <>{children}</>
}
```

Next.js (App Router)
Add a `PageviewTracker` client component that watches `usePathname` and `useSearchParams`. Wrap it in `<Suspense>` (required for static export).

```jsx
// components/PageviewTracker.jsx
'use client'
import { useEffect, useRef } from 'react'
import { usePathname, useSearchParams } from 'next/navigation'

export default function PageviewTracker() {
  const pathname = usePathname()
  const searchParams = useSearchParams()
  const firstRender = useRef(true)

  useEffect(() => {
    if (firstRender.current) {
      firstRender.current = false
      return // Skip: initial view already tracked on load
    }
    window.td?.trackEvent('events', { event_name: 'page_view' })
  }, [pathname, searchParams])

  return null
}

// app/layout.jsx
import { Suspense } from 'react'
import PageviewTracker from '@/components/PageviewTracker'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Suspense fallback={null}>
          <PageviewTracker />
        </Suspense>
        {children}
      </body>
    </html>
  )
}
```

GTM
In Google Tag Manager, create a **Custom HTML** tag that fires on **All Pages** and **History Change** (for SPAs).

```html
<!-- GTM Custom HTML Tag -->
<script>
  if (window.td) {
    window.td.trackEvent('events', { event_name: 'page_view' })
  }
</script>
```

Set the trigger to fire on **All Pages** for MPA sites, or add a **History Change** trigger for single-page applications.

## Step 4 — Set User Identity

The SDK provides two mechanisms for anonymous identity. **Server-Side Cookie (`td_ssc_id`) is recommended** because it is issued by your own server and is not subject to browser ITP restrictions that limit first-party cookies to 7 days.

### Option A — Server-Side Cookie (Recommended)

`td_ssc_id` is a cookie set by your server, not the browser. It persists reliably across Safari ITP and iOS environments and is the preferred identifier for In-Browser Messaging.

**Infrastructure required first**: A small SSC server endpoint must be deployed on your domain (e.g., `ssc.yourdomain.com`). Contact your Customer Success Manager or implementation team for the SSC server setup guide.

Once the SSC infrastructure is in place:

```javascript
const td = new Treasure({
  writeKey: 'YOUR_WRITE_KEY',
  database: 'YOUR_DATABASE',
  host: 'YOUR_REGION_HOST',
  useServerSideCookie: true,
  sscDomain: 'yourdomain.com',   // Your root domain
  personalization: {
    endpoint: 'YOUR_P13N_ENDPOINT',
    token: 'YOUR_WP13N_TOKEN',
  },
})

// Enable signed mode, then fetch the server-side cookie
td.setSignedMode()
td.fetchServerCookie(
  (sscId) => console.log('SSC ID:', sscId),
  (error) => console.error('SSC fetch failed:', error)
)
```

After `fetchServerCookie` succeeds, the `td_ssc_id` is automatically included in all subsequent events.

SSC prerequisites
`fetchServerCookie` requires:

1. `useServerSideCookie: true` in the SDK config
2. `setSignedMode()` called before `fetchServerCookie()`
3. The SSC server endpoint deployed and accessible on your domain


### Option B — First-Party Browser Cookie (Fallback)

If SSC infrastructure is not yet available, the SDK falls back to `td_client_id` — a first-party cookie set by the browser. It works without any server infrastructure, but is subject to ITP restrictions (7-day cap on Safari/iOS).

```javascript
// No extra config needed — td_client_id is managed automatically
const td = new Treasure({
  writeKey: 'YOUR_WRITE_KEY',
  database: 'YOUR_DATABASE',
  host: 'YOUR_REGION_HOST',
  personalization: {
    endpoint: 'YOUR_P13N_ENDPOINT',
    token: 'YOUR_WP13N_TOKEN',
  },
})
```

| **Identifier**  | **Set by**  | **ITP / Safari**  | **Infrastructure**  | **Recommendation**  |
|  --- | --- | --- | --- | --- |
| `td_ssc_id` | Your server | ✅ Not affected | SSC server required | **Recommended** |
| `td_client_id` | Browser (JS) | ⚠️ 7-day cap | None | Fallback only |


### Setting Authenticated User ID

After a user logs in, pass your internal user ID so events can be linked across devices and channels regardless of which cookie is in use.

```javascript
// After login / session restore
td.set('$global', 'td_user_id', 'YOUR_INTERNAL_USER_ID')

// From this point, all events include td_user_id
td.trackEvent('events', { event_name: 'page_view' })
```

Identity design
Use a **stable, non-PII internal ID** (account ID, CRM ID) — not email or phone number. Only set `td_user_id` after the user is authenticated.

### Anonymous vs Signed Mode

By default the SDK runs in **anonymous mode** — identifiers and IP address are not sent. Enable signed mode when the user has consented to tracking.

```javascript
// User has consented — enable PII collection (required for SSC)
td.setSignedMode()

// User has withdrawn consent
td.setAnonymousMode()

// Block all events (e.g., cookie banner rejection)
td.blockEvents()
td.unblockEvents() // Resume
```

## Step 5 — Verify the Setup

Open your browser's DevTools → **Network** tab and filter for requests to your `host` or `personalization.endpoint` domain. After calling `trackEvent`, you should see:

- **Without personalization**: a POST request to the ingest endpoint with the event payload.
- **With personalization**: a POST request to the p13n endpoint; the response contains the `offers` object with any active campaign payloads.


You can also check the **Console** tab — the SDK logs `[td-debug]` messages showing the request URL and payload.

## User Identity and Privacy

### The anonymous-to-identified transition

```
User visits (no login)
  └─ td_ssc_id issued by your server (recommended)
     OR td_client_id set by browser (fallback)
  └─ Events sent anonymously

User logs in
  └─ td.set('$global', 'td_user_id', 'user_12345')
  └─ All subsequent events include both cookie ID + td_user_id
  └─ RT 2.0 ID stitching links the anonymous history to the user
```

### Automatically collected properties

The SDK attaches these fields to every event automatically — no implementation required:

| **Property**  | **Description**  | **PII?**  |
|  --- | --- | --- |
| `td_ssc_id` | Server-side cookie identifier (recommended — not ITP-affected) | Yes (signed mode only) |
| `td_client_id` | First-party browser cookie identifier (fallback — ITP 7-day cap on Safari) | Yes (signed mode only) |
| `td_session_id` | Per-session UUID (future SDK versions) | No |
| `td_url` | Current page URL | Potentially |
| `td_path` | URL path (e.g., `/product/123`) | No |
| `td_host` | Hostname | No |
| `td_referrer` | Referring URL | Potentially |
| `td_title` | Page title | No |
| `td_viewport` | Browser window size (`WxH`) | No |
| `td_screen` | Screen resolution (`WxH`) | No |
| `td_language` | Browser language | No |
| `td_user_agent` | Browser User-Agent string | Potentially |
| `td_version` | SDK version | No |


## Next Steps

- [Define Business Events](/products/marketing-cloud/engage-studio/experiences/event-tracking) — set up `view_item`, `add_to_cart`, `purchase`, and other custom events
- [Create a Popup Campaign](/products/marketing-cloud/engage-studio/experiences/create-a-popup-campaign)
- [Create an Inline Campaign](/products/marketing-cloud/engage-studio/experiences/create-an-inline-campaign)