Skip to main content

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.

This is the 5-minute integration walkthrough. Before you start, make sure you’ve completed Installation.

1. Create a session

The entry point is KontextAds.createSession(context, options), which returns a Session you’ll use for the rest of the conversation lifecycle.
import so.kontext.ads.KontextAds
import so.kontext.ads.model.Character
import so.kontext.ads.model.SessionOptions

val session = KontextAds.createSession(
    context = applicationContext,
    options = SessionOptions(
        publisherToken = "<your-publisher-token>",
        userId = "user-1234",
        conversationId = "conv-5678",
        character = Character(
            id = "character-1234",
            name = "John Doe",
            avatarUrl = "https://example.com/avatar.png",
            greeting = "Hello, how can I help you today?",
        ),
        onEvent = { event ->
            // Handle ad lifecycle events
            android.util.Log.d("kontext", "${event.name} $event")
        },
    ),
)
The session keeps publisherToken, userId, and conversationId fixed for its lifetime. Recreate the session when any of those change (e.g. when the user starts a new chat).
Pass context.applicationContext rather than an Activity context — the Session outlives any single Activity and the SDK manages WebView resources internally.

2. Feed conversation messages

Add every message to the session as it appears. User messages trigger a debounced preload in the background; assistant messages let the SDK link the matched ad to the corresponding placement.
import so.kontext.ads.model.Message
import so.kontext.ads.model.Role

session.addMessage(
    Message(id = "msg-1", role = Role.USER, content = "Hello, how are you?"),
)

session.addMessage(
    Message(id = "msg-2", role = Role.ASSISTANT, content = "I am good, thank you!"),
)
addMessage(...) returns synchronously — it is not a suspend function. The preload result is delivered later via the onEvent callback (Filled, NoFill, Error, …) — not via a return value.

3. Render the ad

The recommended way is the InlineAd composable. It takes the assistant message’s messageId and the Session, and handles the rest:
import androidx.compose.runtime.Composable
import so.kontext.ads.ui.InlineAd

@Composable
fun ChatRow(message: ChatMessage, session: Session) {
    MessageBubble(message)

    if (message.role == Role.ASSISTANT) {
        InlineAd(messageId = message.id, session = session)
    }
}
InlineAd is idempotent across recompositions — under the hood it calls session.createAd(messageId), which returns the same Ad if one already exists for that (messageId, code, theme). Scrolling the ad off-screen and back does not rebuild the iframe. If your app is not on Compose, use InlineAdView instead — see Guides → View interop.

4. Tear down

Call session.destroy() when the conversation ends or the screen is closed. It is idempotent and required to cancel pending network requests and release WebView resources. In Compose:
DisposableEffect(session) {
    onDispose { session.close() } // alias for session.destroy()
}

Observing events

AdEvent is delivered both via the onEvent callback you pass to SessionOptions and via the Session.events Flow — pick whichever fits your codebase. Both deliver the same events on the main thread.

onEvent callback

val session = KontextAds.createSession(
    context = applicationContext,
    options = SessionOptions(
        publisherToken = "...",
        userId = "...",
        conversationId = "...",
        onEvent = { event ->
            when (event) {
                is AdEvent.Filled -> println("ad filled: ${event.bidId} revenue=${event.revenue}")
                is AdEvent.Clicked -> println("clicked: ${event.url}")
                else -> Unit
            }
        },
    ),
)

Flow

import kotlinx.coroutines.flow.collect

lifecycleScope.launch {
    session.events.collect { event ->
        println("event: ${event.name}")
    }
}
The complete event list lives in the API reference.