Skip to Content

Configuration

Sensible defaults, and every option when you need it.

The CopilotKit integration is configured through the AdzenAsyncConfig object passed to the AdzenAsyncMiddleware constructor, or AdzenStreamConfig passed to AdzenStreamMiddleware. Values can be sourced from environment variables, explicit code, or a combination.

Configuration precedence

  1. Explicit values passed to new AdzenAsyncMiddleware({ ... })
  2. Environment variables read by your application
  3. SDK defaults for optional fields

There is no automatic environment variable binding — your application code reads environment variables and passes them to the constructor.

Where the middleware runs

Run AdzenAsyncMiddleware server-side — in the browser it would expose your API key. AdzenStreamMiddleware is server-side only (Node 18+).

Streaming proxies the LLM response stream into the Adzen /stream endpoint by passing a ReadableStream as the fetch request body. Only Chromium-based browsers implement fetch upload streams, and they additionally require HTTP/2. Safari and Firefox silently coerce a stream body to the string "[object ReadableStream]", which the API cannot process.

The SDK feature-detects this. When the runtime cannot send stream bodies, AdzenStreamClient emits an adzen_error with code request_streams_unsupported and skips the request rather than sending a corrupted body.

Since the Adzen API uses key auth, running server-side also keeps your API key out of the client bundle.

Environment variables

These are recommended environment variable names. Your application reads them and passes them to the middleware.

Required

VariablePurpose
ADZEN_API_KEYAdzen API key for your publisher account

Optional

VariableDefaultPurpose
ADZEN_API_URLhttps://api.adzen.ai/v1/aiAdzen API base URL. The SDK appends /process and /stream. The default targets the hosted Adzen API, whose AI endpoints live under the /ai path prefix (so /process resolves to https://api.adzen.ai/v1/ai/process). Override only if Adzen gives you a different base URL.
ADZEN_TIMEOUT_MS3000Maximum time (ms) to wait for the Adzen API before abandoning the ad fetch
ADZEN_AD_UNIT_POSITION"chin"Ad unit position label used in tracking
ADZEN_LOCATION—DMA location code (e.g. "US-CA-803") sent with requests for geo-targeting
ADZEN_PROFILE_ID—Publisher profile ID, sent as the X-Profile-Id header. Only needed if Adzen support asks you to set it.

Example .env

ADZEN_API_KEY=your_api_key ADZEN_TIMEOUT_MS=3000 ADZEN_AD_UNIT_POSITION=chin ADZEN_LOCATION=US-CA-803

Adzen API response format

The Adzen /process API returns:

{ "message_id": "msg_abc123", "ads": [ { "ad_id": 1071, "title": "Local Coffee Co", "cta": "Learn More", "click_through_url": "https://api.adzen.ai/…/redirect/abc1234", "advertiser_name": "Acme Corp", "sponsor_logo_url": "https://example.com/logo.png", "creative_url": "https://example.com/banner.jpg", "description": "A great product", "placement": "after_message", "pixel_trackers": [], "render_impression_url": "https://api.adzen.ai/…/impressions/render", "view_impression_url": "https://api.adzen.ai/…/impressions/view" } ] }

The middleware consumes this format directly and maps it to AdzenPlacement internally:

API fieldSDK field
ads[0].ad_idad_id (coerced to string)
ads[0].titleheadline
ads[0].ctacta_text
ads[0].click_through_urldestination_url
ads[0].advertiser_nameadvertiser_name
ads[0].sponsor_logo_urladvertiser_image_url
ads[0].creative_urlcreative_url
ads[0].descriptiondescription
ads[0].placementadUnitPosition
ads[0].render_impression_urlrender_impression_url
ads[0].view_impression_urlview_impression_url
ads.length === 0No placement event emitted

AdzenAsyncConfig options

Passed to new AdzenAsyncMiddleware(config):

OptionTypeRequiredDefaultDescription
apiKeystringYes—Adzen API key. Sent as the X-API-Key header.
endpointUrlstringNo"https://api.adzen.ai/v1/ai"Adzen API base URL. The middleware appends /process when calling the ad matching endpoint. The default targets the hosted API (AI endpoints under the /ai prefix); override only if Adzen gives you a different base URL.
timeoutMsnumberNo3000Maximum time (ms) to wait for the /process API response. Uses AbortController to cancel the fetch on timeout.
adUnitPositionstringNo"chin"Ad unit position label for this placement.
locationstringNo—DMA location code sent as a top-level field on the /process request for geo-targeting.
conversationIdstringNo—Conversation identifier sent as conversation_id on the /process request. Links impressions to conversation context. If a RUN_STARTED event has a threadId, it overrides this value.
profileIdstringNo—Publisher profile ID, sent as the X-Profile-Id header. Only needed if Adzen support asks you to set it.
import { AdzenAsyncMiddleware } from "@adzenai/ai/copilotkit"; const adzen = new AdzenAsyncMiddleware({ apiKey: process.env.ADZEN_API_KEY!, location: process.env.ADZEN_LOCATION, });

With conversation tracking

const adzen = new AdzenAsyncMiddleware({ apiKey: process.env.ADZEN_API_KEY!, location: "US-CA-803", conversationId: session.conversationId, });

If you pass AG-UI RUN_STARTED events through the middleware and they contain a threadId field, the middleware automatically uses that as the conversation_id — overriding any value set in the config.

AdzenStreamConfig options

Coming soon. Streaming enrichment is not yet available in production. The async middleware is the supported path today; this section describes the streaming mode so you can plan for it.

Passed to new AdzenStreamMiddleware(config) (server-side only). AdzenStreamMiddlewareConfig extends AdzenStreamConfig with adUnitPosition.

OptionTypeRequiredDefaultDescription
apiKeystringYes—Adzen API key. Sent as the X-API-Key header.
endpointUrlstringNo"https://api.adzen.ai/v1/ai"Adzen API base URL. The SDK appends /process and /stream. The default targets the hosted API (AI endpoints under the /ai prefix); override only if Adzen gives you a different base URL.
timeoutMsnumberNo3000Maximum time (ms) to wait for the Adzen API before aborting via AbortController.
locationstringNo—DMA location code. Sent as the X-Location header on /stream and a top-level field on /process.
conversationIdstringNoauto (UUID)Conversation identifier. Overridden by an AG-UI RUN_STARTED threadId when present.
profileIdstringNo—Publisher profile ID, sent as the X-Profile-Id header.
placementPollIntervalMsnumberNo2000Interval (ms) for polling the sidecar placement endpoint.
prefetchbooleanNofalseWhen true, fires POST /process with the prompt before opening the stream.
sidecarbooleanNotrueWhether to poll the sidecar placement endpoint for out-of-stream ads. Set to false for inline-only.
adUnitPositionstringNo"chin"Ad unit position label for this placement.

AdzenCardProps options

Passed to <AdzenCard>:

PropTypeRequiredDefaultDescription
messageIdstringYes—The assistant message ID to look up an ad placement for.
adUnitPositionstringNo"chin"Ad unit position label for this placement.
viewabilityThresholdMsnumberNo1000Milliseconds the ad must be continuously visible before a view impression fires.
classNamestringNo—CSS class applied to the card’s root <div>.

Example

<AdzenCard messageId={msg.id} adUnitPosition="chin" viewabilityThresholdMs={2000} className="my-ad-card" />

useAdzenPlacement return type

interface UseAdzenPlacementReturn { getAdForMessage(messageId: string): AdzenPlacement | null; placements: Map<string, AdzenPlacement>; }
PropertyDescription
getAdForMessageReturns the AdzenPlacement for a given messageId, or null if no ad was matched.
placementsThe full Map of all received placements, keyed by messageId.

Bridge functions

dispatchPlacementEvent(event)

Checks whether a single AG-UI event is an adzen_placement custom event and dispatches it as a browser CustomEvent on window (picked up by useAdzenPlacement). Returns true if the event was dispatched, false otherwise.

dispatchInlineAdEvent(event)

Checks whether a single AG-UI event is an adzen_inline_ad custom event and dispatches it as a browser CustomEvent on window (picked up by useAdzenInlineAds). Returns true if the event was dispatched, false otherwise. These events are emitted only by AdzenStreamMiddleware.

dispatchPlacementEvents(events)

Convenience wrapper that iterates an array of AG-UI events and calls both dispatchPlacementEvent and dispatchInlineAdEvent for each — so it bridges both adzen_placement cards and adzen_inline_ad in-message creatives. Safe to call on every batch of events from processEvent() or processStream(); events that are neither are silently ignored.

All three functions require a browser environment (window must be defined). They are no-ops in server-side contexts.

Viewability configuration

Viewability tracking uses IntersectionObserver via @adzenai/core’s observeViewability():

SettingDefaultDescription
Intersection threshold1.0The ad must be 100% visible in the viewport.
Time threshold1000msThe ad must be continuously visible for this duration.
Firing behaviorOnceThe impression fires once per ad instance.

The intersection threshold is not configurable via AdzenCard — it always requires full visibility. The time threshold is configurable via viewabilityThresholdMs.

Impression beacons

AdzenCard fires two impression beacons as client-side GET requests directly to the delivery API. No server-side proxy or authentication is required. The impression URLs are pre-built by the API and fired as-is.

Render impression — fired on mount (when the ad first becomes available). Sent as a GET to the pre-built render_impression_url.

View impression — fired after the viewability threshold is met. Sent as a GET to the pre-built view_impression_url.

Null impression URLs

If render_impression_url or view_impression_url is null (impression tracking not configured for this ad), the corresponding beacon is skipped entirely.

Error handling and silent degradation

Adzen never throws errors into the AG-UI event stream or the React rendering tree. All failures are silent:

ScenarioBehavior
/process API returns non-200The middleware emits no placement event
/process API times outAbortController cancels the request → no placement event
Network error during ad fetchThe middleware emits no placement event
/process API returns empty ads arrayNo placement event emitted
Impression beacon fails (render or view)Single retry after 1 second; failure is swallowed
window undefined (SSR)dispatchPlacementEvent returns false — no-op

Timeout and retry policy

Ad fetch

  • Timeout: Controlled by timeoutMs (default 3000ms). Uses AbortController to cancel the fetch.
  • Retries: None. A timed-out or failed ad fetch is abandoned. The ad slot for that message is forfeited.

Impression beacon

  • Timeout: No explicit timeout on the impression beacon fetch.
  • Retries: One automatic retry after 1 second on failure. If the retry also fails, the error is swallowed.

RUN_FINISHED hold

RUN_FINISHED events are held until all pending ad fetches settle via Promise.allSettled. This ensures the stream does not terminate before ad placements are emitted. The hold duration is bounded by the timeoutMs of the slowest pending fetch.

Stale buffer eviction

On each TEXT_MESSAGE_START, the middleware evicts any buffer entries older than timeoutMs * 2. This prevents memory leaks from incomplete message sequences (e.g., a TEXT_MESSAGE_START without a corresponding TEXT_MESSAGE_END).

Next

Last updated on