Engineering
Adding ads to a streaming LLM response
21 July 2026 · 6 min read · Monetzly team
The integration is deliberately small: you already have a token stream, and the SDK returns a token stream. Everything else — matching, disclosure, attribution — happens behind that boundary.
This walkthrough uses Node with a streaming chat route, which is what the demo on this site runs. The shape is the same in any runtime that can iterate an async stream.
1. Install and configure
npm install @monetzly/server-sdkConfigure once at module scope, not per request — the client holds a connection you want to reuse across invocations.
import { MonetzlySDK, MonetzlyConfig } from "@monetzly/server-sdk";
const config: MonetzlyConfig = {
apiKey: process.env.MONETZLY_API_KEY!,
serverAddress: process.env.MONETZLY_SERVER_ADDRESS!,
useSSL: true,
timeout: 30000,
};
const sdk = new MonetzlySDK(config);2. Wrap the stream
Call connect() before the first injection, then pass your model's token stream through inject(). What comes back is an async generator of the same chunk shape, so your existing forwarding loop keeps working.
await sdk.connect();
const llmStream = await llm.stream(messages);
for await (const chunk of sdk.inject(llmStream, {
sessionId,
metadata: { app: "my-assistant" },
})) {
const text = chunk.text ?? chunk.content ?? "";
if (text) controller.enqueue(encoder.encode(text));
}If no campaign matches the conversation — the common case — the chunks come through unchanged and the user sees exactly what your model produced.
3. Keep the failure path boring
Monetization must never be able to take down the product. Two rules cover almost all of it.
- Treat the ad layer as optional: on any error or timeout, fall back to streaming your original response. A missed impression is a rounding error; a failed conversation is a churned user.
- Keep the timeout tight relative to your own response latency, so a slow match can never become the reason the first token is late.
let stream: AsyncIterable<{ text?: string; content?: string }> = llmStream;
try {
await sdk.connect();
stream = sdk.inject(llmStream, { sessionId });
} catch (err) {
console.error("monetzly unavailable, streaming unmodified response", err);
}
for await (const chunk of stream) {
/* forward as before */
}4. Verify before you ship
- Run a conversation with obvious commercial intent and confirm a labelled sponsored mention appears.
- Run a conversation with none and confirm the response is byte-identical to the unwrapped version.
- Kill network access to the ad service and confirm responses still stream.
- Check that impressions show up against the session id you passed.
Monetize the conversations you already have
Wrap your existing response stream with the Monetzly SDK and earn on sessions that were never going to convert to a subscription.