# Advertiser SDK: Integration Guide (v1)

The Adgeist Advertiser SDK attributes visits to your Adgeist campaigns and
records conversions. You add one script tag. The SDK tracks visits on its own,
and you call its global functions to identify users and record conversions.

|              |                            |
| ------------ | -------------------------- |
| Global       | `window.adgeistAdvertiser` |
| Bundle size  | ~17 kB gzipped             |
| Dependencies | none                       |

**Steps**

1. [Integrate the SDK](#step-1-integrate-the-sdk)
2. [Access the SDK](#step-2-access-the-sdk)
3. [Global functions](#step-3-global-functions): `setUserDetails`,
   `trackConversionEvent`, `setConsentConfig`
4. [Version](#step-4-version)

**Reference:** [API reference](#api-reference) ·
[How it works](#appendix-how-it-works) · [Troubleshooting](#troubleshooting)

---

## Step 1: Integrate the SDK

### 1.1 Register your domains

Adgeist only accepts events from domains registered to your advertiser account.
Register every origin you run the SDK on, **including local development
origins** such as `http://localhost:5173`.

Events from an unregistered origin are rejected with:

```
HTTP 406 Not Acceptable
{"Status":"error","Error":"invalid analytics origin"}
```

This error is silent: it only shows in the browser's Network panel. See
[Troubleshooting](#troubleshooting).

### 1.2 Add the script tag

**Where:** once, in your site's root file: the shared layout or template that
every page uses (`index.html`, `app/layout.tsx`, a base template). The SDK must
run on **every page**, not only the conversion page: it reads the campaign
parameters on the landing page and sends conversions from the conversion page.
Putting it in the root file covers both.

```html
<head>
  <script
    src="https://cdn.adgeist.ai/advertiser/v1/advertiser.min.js"
    data-advertiser-id="YOUR_ADVERTISER_ID"
    async
  ></script>
</head>
```

**No `init()` call.** The SDK starts on its own when the script loads.

| Attribute            | Required | Purpose                                        |
| -------------------- | -------- | ---------------------------------------------- |
| `data-advertiser-id` | **yes**  | Your advertiser ID from the Adgeist dashboard. |

**`data-advertiser-id` is mandatory.** Without it nothing is tracked: no visits
and no conversions. Your page keeps working, and calls to the SDK do nothing
(`trackConversionEvent()` resolves `false`).

#### Channels

| Channel  | URL                                                                    | Use for                                    |
| -------- | ---------------------------------------------------------------------- | ------------------------------------------ |
| Floating | `cdn.adgeist.ai/advertiser/v1/advertiser.min.js`                       | production; receives every `1.x.y` release |
| Pinned   | `cdn.adgeist.ai/advertiser/<version>/advertiser.min.js` (e.g. `1.1.5`) | a fixed build; required for SRI            |
| Beta     | `cdn-beta.adgeist.ai/advertiser/v1-beta/advertiser.min.js`             | pre-release testing                        |

`v1` is a **floating channel**: you automatically receive fixes and
backward-compatible features. Breaking changes ship to a new channel (`v2`) that
you opt into explicitly.

### 1.3 Framework setup

**Next.js (App Router).** Add the script to the root layout so it loads once
and stays loaded across client-side navigation:

```tsx
// app/layout.tsx
import Script from "next/script";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script
          async
          src="https://cdn.adgeist.ai/advertiser/v1/advertiser.min.js"
          data-advertiser-id="YOUR_ADVERTISER_ID"
        />
      </body>
    </html>
  );
}
```

**React (Vite, Create React App) or any SPA.** Put the script tag from 1.2 in
`index.html`. Do not inject it from a component that mounts and unmounts.

Route changes in single-page apps are tracked for you; see
[Single-page apps](#single-page-apps).

---

## Step 2: Access the SDK

The SDK exposes one global object, `window.adgeistAdvertiser`. Every function in
[Step 3](#step-3-global-functions) is called on it.

### 2.1 When the global is available

The script loads `async`, so `window.adgeistAdvertiser` is `undefined` until the
script has loaded. Check for it before calling:

```js
if (window.adgeistAdvertiser) {
  window.adgeistAdvertiser.trackConversionEvent("signup");
}
```

### 2.2 TypeScript

Declare the global once, for example in `adgeist.d.ts`:

```ts
export {};

declare global {
  interface Window {
    adgeistAdvertiser?: {
      setUserDetails(details: {
        userId: string;
        email?: string;
        phone?: string;
        companyName?: string;
      }): Promise<void>;
      trackConversionEvent(
        eventType: string,
        payload?: Record<string, unknown>,
      ): Promise<boolean>;
      setConsentConfig(config: Record<string, unknown>): void;
      version: string;
    };
  }
}
```

### 2.3 Error handling

SDK calls never throw errors into your page, so you don't need `try`/`catch`
around them. If a call fails, it returns its fallback value: `false` for
`trackConversionEvent`, `undefined` for the others.

---

## Step 3: Global functions

Call these in this order in your integration. Each one is a method on
`window.adgeistAdvertiser`.

| Function                                           | Purpose                        | Returns            |
| -------------------------------------------------- | ------------------------------ | ------------------ |
| [`setUserDetails`](#31-setuserdetails)             | Link this browser to your user | `Promise<void>`    |
| [`trackConversionEvent`](#32-trackconversionevent) | Record a conversion            | `Promise<boolean>` |
| [`setConsentConfig`](#33-setconsentconfig)         | Configure consent behavior     | `void`             |

### 3.1 `setUserDetails`

**Purpose.** Tells Adgeist who the user is, so visits and conversions from this
browser are linked to your user ID. `email` and `phone` also link the user to
their Adgeist CDP profile.

**When to call.** Once, when you know who the user is, for example right after
login or signup. You don't need to call it again on other pages.

**Where to call.** In your login/signup success handler, or wherever your app
first has the signed-in user.

**Signature**

```ts
setUserDetails(details: {
  userId: string;
  email?: string;
  phone?: string;
  companyName?: string;
}): Promise<void>
```

**Parameters**

| Field         | Type     | Required | Notes                                                                                                    |
| ------------- | -------- | -------- | -------------------------------------------------------------------------------------------------------- |
| `userId`      | `string` | yes      | Your stable ID for the user.                                                                             |
| `email`       | `string` | no       | As the user typed it. Also links the user to their Adgeist CDP profile.                                  |
| `phone`       | `string` | no       | **Must include the country code** (`+91…`); spaces and dashes are fine. Links the user to their profile. |
| `companyName` | `string` | no       | Your brand name; defaults to your domain.                                                                |

Other fields are ignored.

**Returns.** `Promise<void>`. Never rejects.

**Behavior**

- **Updates, doesn't replace.** Each call adds to the details from earlier
  calls, so a field you leave out keeps its previous value.
- **Safe to repeat.** Calling it again with the same details does nothing.
- **Works regardless of consent.**
- **Invalid values are skipped, not fatal.** An email or phone Adgeist can't use
  (e.g. a phone without a country code) is ignored and a warning is logged to
  the console; the rest of the call still applies.

Email, phone and user ID are personal data sent to Adgeist. Only pass them when
your privacy policy covers it.

**Example**

```js
async function onLoginSuccess(user) {
  if (!window.adgeistAdvertiser) return;
  await window.adgeistAdvertiser.setUserDetails({
    userId: user.id, // 'u_10293'
    email: user.email, // 'user@example.com'
    phone: user.phone, // '+91 98765 43210'
    companyName: "Acme Store",
  });
}
```

### 3.2 `trackConversionEvent`

**Purpose.** Records a conversion and attributes it to the campaign that
brought the user. You decide what counts as a conversion: a purchase, a signup,
a lead, a button click, a page view, or any other action.

**When to call.** When the user completes the action you want to count.

**Where to call.** In the code that handles that action, for example a click
handler, a form submit handler, or the page you want to count.

**Signature**

```ts
trackConversionEvent(
  eventType: string,
  payload?: Record<string, unknown>,
): Promise<boolean>
```

**Parameters**

| Parameter   | Type     | Required | Notes                                                          |
| ----------- | -------- | -------- | -------------------------------------------------------------- |
| `eventType` | `string` | yes      | Any name you choose: `"purchase"`, `"signup"`, `"lead"`.       |
| `payload`   | `object` | no       | Any data about the conversion, e.g. order ID, value, currency. |

**Returns.** `Promise<boolean>`:

- `true` when the conversion was recorded, or saved to be sent later.
- `false` when it was rejected (for example, the domain isn't registered).

On a slow connection it can take up to ~45 s to settle, so don't `await` it
right before navigating away. A conversion that couldn't be sent right away is
sent later automatically; see [Delivery](#delivery).

**Behavior**

- **Every call counts.** Two `trackConversionEvent('purchase', …)` calls are
  two conversions. If your code can fire the same event twice by accident (a
  re-render, a double-submit), guard it on your side.

**Example**

```js
async function onOrderPlaced(order) {
  if (!window.adgeistAdvertiser) return;
  const ok = await window.adgeistAdvertiser.trackConversionEvent("purchase", {
    orderId: order.id, // 'A-1029'
    value: order.total, // 4999
    currency: "INR",
  });
  // ok === false: rejected. Nothing to retry on your side.
}
```

### 3.3 `setConsentConfig`

**Purpose.** Configures consent for the Adgeist SDKs: the consent mode, whether
the built-in banner shows, and its texts and theme.

Consent has two categories: `necessary` (always on) and `personalization`, which
the user can turn on or off. `personalization` controls whether device details
and Adgeist CDP data are collected. Visits and conversions are tracked either
way; consent only controls the extra details. If the user withdraws consent,
collection stops from that point on.

**When to call.** **Before** the SDK loads, so the config applies from the
first page view.

**Where to call.** In an inline `<script>` placed **above** the advertiser
script tag. Set the config as an object:

```html
<script>
  window.adgeistConsentConfig = {
    mode: "opt-in",
    autoShow: true,
    policyUrl: "https://example.com/privacy",
  };
</script>
<!-- then the advertiser <script> tag -->
```

or as a queued command:

```html
<script>
  window.adgeistConsent = [["setConsentConfig", { mode: "opt-in" }]];
</script>
```

`window.adgeistAdvertiser.setConsentConfig(config)` is for changing the config
after load. The new config takes full effect from the next page load.

**Signature**

```ts
setConsentConfig(config: ConsentConfig): void
```

**Parameters.** `config` fields (all optional):

| Field       | Type                    | Default     | Notes                                                                                                  |
| ----------- | ----------------------- | ----------- | ------------------------------------------------------------------------------------------------------ |
| `mode`      | `'opt-in' \| 'opt-out'` | `'opt-out'` | `opt-out`: collection runs until the user rejects. `opt-in`: collection is off until the user accepts. |
| `autoShow`  | `boolean`               | `false`     | Show the banner automatically when the user hasn't made a choice yet.                                  |
| `ui`        | `'default' \| 'none'`   | `'default'` | `'none'` to bring your own UI.                                                                         |
| `policyUrl` | `string`                |             | Link shown in the banner.                                                                              |
| `persist`   | `boolean`               | `true`      | `false` stops the config from being remembered on later page loads.                                    |

Further options include `texts`, `theme`, `cookieName`, `categories` and
`onConsentChange`.

**Returns.** `void`.

**Behavior**

- **Updates, doesn't replace.** Each call adds to the config from earlier
  calls.

**Example**

```js
if (window.adgeistAdvertiser) {
  window.adgeistAdvertiser.setConsentConfig({ mode: "opt-in", autoShow: true });
}
```

---

## Step 4: Version

**Purpose.** Tells you which SDK version is running on the page.

```ts
window.adgeistAdvertiser.version: string // e.g. '1.1.5'
```

It is a property, not a function. Use it when debugging or reporting an issue:

```js
console.log(window.adgeistAdvertiser && window.adgeistAdvertiser.version);
```

**Which version you get** depends on the script URL
([channels](#channels)):

- `v1` (floating): the latest `1.x.y`. Fixes and backward-compatible features
  arrive automatically. Breaking changes ship as `v2`, which you opt into.
- `<version>` (pinned): exactly that version, never changes. Required for SRI.
- `v1-beta`: pre-release versions.

Beta and production share version numbers; only the script URL
(`cdn-beta.adgeist.ai/.../v1-beta` vs `cdn.adgeist.ai/.../v1`) tells them
apart.

---

## API reference

```ts
window.adgeistAdvertiser.setUserDetails(details: {
  userId: string;
  email?: string;
  phone?: string;
  companyName?: string;
}): Promise<void>

window.adgeistAdvertiser.trackConversionEvent(
  eventType: string,
  payload?: Record<string, unknown>,
): Promise<boolean>

window.adgeistAdvertiser.setConsentConfig(config: ConsentConfig): void

window.adgeistAdvertiser.version: string
```

---

## Appendix: How it works

### Events sent automatically

You don't call these; the SDK sends them on its own.

| Event         | When                                                                               |
| ------------- | ---------------------------------------------------------------------------------- |
| `VISIT`       | Once per page load.                                                                |
| `ENGAGEMENT`  | When the user moves to a new page in a single-page app, and after each conversion. |
| `SESSION_END` | When the user leaves the page.                                                     |

**A rejected `VISIT` stops the rest.** If the visit is rejected (for example,
the domain isn't registered), no further automatic events are sent from that
page. Conversions are still sent.

### Single-page apps

Client-side route changes are tracked automatically. React Router, Next.js and
Vue Router need no extra setup. Re-rendering the same URL doesn't count as a
new page.

### Campaign attribution

When a user lands from an Adgeist campaign link, the SDK reads the campaign
parameters (`utm_source`, `utm_campaign`, `utm_data`) from the URL and remembers
them for **7 days**. A user who arrives from a campaign, leaves, and comes back
on their own within 7 days is still attributed to that campaign. A new campaign
link replaces the earlier attribution and restarts the 7 days.

### Delivery

You don't need your own retry logic. If an event can't be sent (a flaky
connection, the user going offline), the SDK retries it, and if it still can't
get through, sends it on a later visit, for up to 7 days. Events are never
counted twice.

---

## Troubleshooting

### No events at all after `VISIT`

Almost always a rejected visit. In the browser's Network panel, filter by
`adgeist`, find the event request and check its status:

| Status                             | Meaning               | Fix                                      |
| ---------------------------------- | --------------------- | ---------------------------------------- |
| `406` `"invalid analytics origin"` | Origin not registered | Register the origin, including localhost |
| `200`                              | Delivered             | Look elsewhere; see below                |

### `SESSION_END` never appears

Three things to rule out, in order:

1. **Filter by URL, not request type.** Filter the Network panel by `adgeist`
   rather than by a request type such as "ping".
2. **A link click inside a single-page app isn't leaving the page.** It sends
   `ENGAGEMENT`, not `SESSION_END`. Close the tab or go to another site to test.
3. **Check `VISIT` succeeded**, per above. After a rejected visit, `SESSION_END`
   isn't sent.

### The request shows status "unknown" in DevTools

Expected for requests sent while a page is closing: DevTools stops tracking them
before the response arrives. Use `chrome://net-export` instead: start logging,
navigate away, stop logging, then open the file at
<https://netlog-viewer.appspot.com/>. It shows the real status.
