> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kontext.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Showing your first ad

> Create a session, feed messages, mount an ad, observe events.

This is the 5-minute integration walkthrough. Before you start, make sure you've completed [Installation](/sdk/js/installation).

## 1. Create a session

Import `createSession()` to configure a session for a specific user and conversation. The returned `Session` is the entry point for everything else — feeding messages, creating ads, listening to events.

```ts theme={null}
import { createSession } from '@kontextso/sdk-js'

const session = createSession({
  publisherToken: '<your-publisher-token>',
  userId: 'user-1234',
  conversationId: 'conv-5678',
  character: {
    id: 'character-1234',
    name: 'John Doe',
    avatarUrl: 'https://example.com/avatar.png',
    greeting: 'Hello, how can I help you today?',
  },
  onEvent: ({ name, code, payload }) => {
    // process events
  },
})
```

If you're using the CDN global:

```js theme={null}
const { createSession } = window.KontextSdk
const session = createSession({ /* ... */ })
```

**Notes:**

* `userId` should stay the same for the same user across sessions/devices — it powers personalization, frequency capping, and rewarded ads.
* `conversationId` should be unique per chat thread (a single chat room, support ticket, or conversation in your app).

## 2. Feed messages to the SDK

The SDK preloads ads in response to new chat messages. Call `addMessage()` whenever a user or assistant message is created — both roles are needed for Kontext to understand conversation context.

```ts theme={null}
// User message
session.addMessage({
  id: 'msg-1',
  role: 'user',
  content: 'Hello, how are you?',
  createdAt: new Date(),
})

// Assistant reply
session.addMessage({
  id: 'msg-2',
  role: 'assistant',
  content: 'I am good, thank you!',
  createdAt: new Date(),
})
```

**Notes:**

* `id` must be unique per message and stable — the same id is referenced later when you create an ad.

## 3. Mount an ad

To display an ad, create an `Ad` instance bound to the assistant message it should appear under, then mount it into a DOM element. The SDK manages the iframe and its lifecycle.

```html theme={null}
<div id="ad-msg-2"></div>

<script type="module">
  // ... session + messages from previous steps

  const ad = session.createAd('msg-2') // messageId of the assistant message
  ad.mount(document.getElementById('ad-msg-2'))
</script>
```

In a typical chat UI you'll:

1. Render your message list.
2. For each assistant message, render a placeholder `<div>` for the ad.
3. Call `session.createAd(messageId)` once the placeholder DOM exists, then `ad.mount(element)`.
4. When the message scrolls off-screen or the conversation ends, call `ad.destroy()` to release the iframe.

```ts theme={null}
const ad = session.createAd('msg-2', { theme: 'dark' })
ad.mount(document.getElementById('ad-msg-2'))
// later, when the message is removed:
ad.destroy()
```

If you'd rather do it in one call, `session.render({ messageId, element, theme })` is a convenience wrapper around `createAd + mount`.

## 4. Tear down

Call `session.destroy()` when the conversation ends or the page unmounts. Idempotent and required to cancel in-flight preloads and release iframe resources.

```ts theme={null}
session.destroy()
```

## Observing events

Subscribe to the lifecycle of every ad via the `onEvent` callback. Every event has a stable string `name` and a typed `payload`:

```ts theme={null}
const session = createSession({
  // ...
  onEvent: (event) => {
    switch (event.name) {
      case 'ad.filled':
        console.log('filled:', event.payload.id, 'revenue=', event.payload.revenue)
        break
      case 'ad.clicked':
        console.log('clicked:', event.payload.url)
        break
      case 'ad.no-fill':
        console.log('no fill, skipCode=', event.payload.skipCode)
        break
    }
  },
})
```

Every placement-attributed event (`ad.filled`, `ad.viewed`, `ad.clicked`, `ad.render-*`, `video.*`, `reward.granted`) also carries a top-level `code` field naming the matched placement, so publishers with multiple `enabledPlacementCodes` can disambiguate. Session-wide events (`ad.no-fill`, `ad.error`) omit it.

For SDK-internal diagnostics during integration, also pass an `onDebugEvent` callback — it receives `(name, data?)` for every internal step the SDK takes.
