English | 日本語
A TypeScript SDK for integrating contextual, in-chat ads into web applications.
⚠️ Language Support: Currently supports English and Japanese for ad decisioning and creatives. Additional languages coming soon.
- Installation
- Quick Start
- API Reference
- Ad Formats
- Event Tracking
- TypeScript Types
- Error Handling
- Examples
- Local Development
npm install @ceedhq/ads-web-sdkimport { initialize, showAd } from "@ceedhq/ads-web-sdk";
// 1. Initialize once on app load
initialize("your-app-id");
// 2. Show an ad after user message
await showAd({
conversationId: "chat-123",
messageId: crypto.randomUUID(),
contextText: "I want to learn programming",
targetElement: document.getElementById("ad-slot"),
});Required before any other SDK calls.
Sets up global configuration for all subsequent API requests.
initialize(appId: string, apiBaseUrl?: string): void| Parameter | Type | Required | Description |
|---|---|---|---|
appId |
string |
Yes | Your application identifier |
apiBaseUrl |
string |
No | Override API URL (for development) |
Example:
// Production (uses default API)
initialize("my-app");
// Development (local API)
initialize("my-app", "/api");Fetches an ad based on conversation context. Does NOT render anything.
Use this when you need full control over rendering.
async requestAd(options: {
conversationId: string;
messageId: string;
contextText: string;
userId?: string;
formats?: AdFormat[];
}): Promise<{ ad: ResolvedAd | null; requestId: string | null }>| Parameter | Type | Required | Description |
|---|---|---|---|
conversationId |
string |
Yes | Unique ID for the chat session |
messageId |
string |
Yes | Unique ID for the message |
contextText |
string |
Yes | User message text for keyword matching |
userId |
string |
No | Optional user identifier |
formats |
AdFormat[] |
No | Array of ad formats to request (defaults to all) |
Example:
const { ad, requestId } = await requestAd({
conversationId: "chat-123",
messageId: crypto.randomUUID(),
contextText: "How do I book a flight?",
});
if (ad) {
console.log(`Ad format: ${ad.format}`);
console.log(`Advertiser: ${ad.advertiserName}`);
}Renders an ad into the DOM and automatically tracks impressions and clicks.
renderAd(
ad: ResolvedAd,
targetElement: HTMLElement,
requestId?: string | null
): RenderedAd| Parameter | Type | Required | Description |
|---|---|---|---|
ad |
ResolvedAd |
Yes | The ad object from requestAd() |
targetElement |
HTMLElement |
Yes | DOM element to render into |
requestId |
string | null |
No | Request ID for event tracking |
Example:
const container = document.getElementById("ad-slot");
container.innerHTML = ""; // Clear previous ad
renderAd(ad, container, requestId);Convenience method that combines fetch + render + tracking in one call.
This is the simplest way to integrate ads.
async showAd(options: {
conversationId: string;
messageId: string;
contextText: string;
targetElement: HTMLElement;
userId?: string;
formats?: AdFormat[];
}): Promise<void>Example:
await showAd({
conversationId: "chat-123",
messageId: crypto.randomUUID(),
contextText: userMessage,
targetElement: document.getElementById("ad-slot"),
});The SDK supports four ad formats, each designed for different use cases.
Default format — A card with title, description, and CTA button.
┌─────────────────────────────────┐
│ ● Advertiser Name Ad │
├─────────────────────────────────┤
│ Ad Title │
│ Ad description text goes │
│ here with supporting details. │
│ │
│ ┌─────────────────────────────┐ │
│ │ Call to Action │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────┘
Use case: Standard promotional ads with a clear call-to-action.
Behavior:
- Impression tracked on render
- Click tracked when CTA button is clicked
- Opens
ctaUrlin a new tab
Lead generation format — Email capture form with success message.
┌─────────────────────────────────┐
│ ● Advertiser Name Ad │
├─────────────────────────────────┤
│ Get Our Free Guide │
│ Enter your email to download │
│ the complete tutorial. │
│ │
│ ┌─────────────────────────────┐ │
│ │ Enter your email... │ │
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │ Subscribe │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────┘
After submit:
┌─────────────────────────────────┐
│ ✓ Thanks! Check your inbox. │
└─────────────────────────────────┘
Use case: Newsletter signups, lead capture, content downloads.
Behavior:
- Impression tracked on render
- Submit event tracked with email when form is submitted
- Success message displayed after submission
- Form input supports
autocompleteattribute
Required config:
leadGenConfig: {
placeholder: string; // Input placeholder text
submitButtonText: string; // Button label
autocompleteType: "email" | "name" | "tel" | "off";
successMessage: string; // Shown after submit
}Display format — Renders immediately on page load, typically below the text input field.
┌─────────────────────────────────┐
│ Ask AI anything... [+] │ ← Input field
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ ● Advertiser Name Ad │
├─────────────────────────────────┤
│ Special Offer │
│ Limited time discount on │
│ selected products. │
│ │
│ ┌─────────────────────────────┐ │
│ │ Shop Now │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────┘
Use case: User-targeted ads displayed around text input interfaces, based on user's previous conversations and ad behavior data.
Behavior:
- Renders immediately on page load before any user interaction
- Identical card rendering to
action_card - Different targeting logic on backend (user history, keywords, geo, device type)
- Impression tracked on render
- Click tracked on CTA click
Optional config:
staticConfig: {
displayPosition: "top" | "bottom" | "inline" | "sidebar";
targetingParams?: {
keywords?: string[];
geo?: string[];
deviceTypes?: ("desktop" | "mobile" | "tablet")[];
}
}Sponsored question format — Tappable question card for conversation flow.
┌─────────────────────────────────┐
│ ● Advertiser Name Ad │
├─────────────────────────────────┤
│ Want to learn more about │
│ our language courses? │
│ │
│ → Tap to learn more │
└─────────────────────────────────┘
Use case: Suggested follow-up questions sponsored by advertisers.
Behavior:
- Entire card is tappable (not just a button)
- Hover effect on card border
- Click event tracked on tap
- Tap action configurable:
redirect: Opens URL in new tabexpand: Host app handles expansionsubmit: Host app handles submission
Required config:
followupConfig: {
questionText: string; // The sponsored question
tapAction: "expand" | "redirect" | "submit";
tapActionUrl?: string; // Required if tapAction is "redirect"
}Developers can use the formats parameter to specify which ad formats they want to receive.
// Request only action_card and lead_gen formats
const { ad, requestId } = await requestAd({
conversationId: "chat-123",
messageId: crypto.randomUUID(),
contextText: "I want to learn English",
formats: ["action_card", "lead_gen"],
});
// Also works with showAd
await showAd({
conversationId: "chat-123",
messageId: crypto.randomUUID(),
contextText: "I want to learn English",
targetElement: document.getElementById("ad-slot"),
formats: ["action_card", "lead_gen"],
});// If formats is not specified, all formats are eligible
const { ad, requestId } = await requestAd({
conversationId: "chat-123",
messageId: crypto.randomUUID(),
contextText: "I want to learn English",
});| Value | Description |
|---|---|
"action_card" |
Standard CTA card |
"lead_gen" |
Email capture form |
"static" |
Banner-style ad |
"followup" |
Sponsored question |
The SDK automatically tracks the following events:
| Event | Trigger | Description |
|---|---|---|
impression |
On render | Ad was displayed to user |
click |
On CTA click | User clicked the CTA button |
submit |
On form submit | User submitted lead_gen form |
Automatic deduplication: Impressions are deduplicated per ad+requestId pair to prevent duplicate tracking (e.g., React StrictMode double renders).
If you need manual control, import individual renderers:
import {
renderActionCard,
renderLeadGenCard,
renderStaticCard,
renderFollowupCard,
} from "@ceedhq/ads-web-sdk";import type {
ResolvedAd,
AdFormat,
ResolvedLeadGenConfig,
ResolvedFollowupConfig,
StaticConfig,
} from "@ceedhq/ads-web-sdk";The main ad payload returned from requestAd():
interface ResolvedAd {
id: string;
advertiserId: string;
advertiserName: string;
format: AdFormat; // "action_card" | "lead_gen" | "static" | "followup"
title: string;
description: string;
ctaText: string;
ctaUrl: string;
leadGenConfig?: ResolvedLeadGenConfig;
staticConfig?: StaticConfig;
followupConfig?: ResolvedFollowupConfig;
}// Lead Gen
interface ResolvedLeadGenConfig {
placeholder: string;
submitButtonText: string;
autocompleteType: "email" | "name" | "tel" | "off";
successMessage: string;
}
// Static
interface StaticConfig {
displayPosition: "top" | "bottom" | "inline" | "sidebar";
targetingParams?: {
keywords?: string[];
geo?: string[];
deviceTypes?: ("desktop" | "mobile" | "tablet")[];
};
}
// Followup
interface ResolvedFollowupConfig {
questionText: string;
tapAction: "expand" | "redirect" | "submit";
tapActionUrl?: string;
}try {
initialize(""); // Empty appId
} catch (error) {
// "CeedAds.initialize: appId is required"
}const { ad, requestId } = await requestAd({...});
if (!ad) {
// No matching ad for this context
// This is normal — ads are not always available
return;
}// If ad.format is "lead_gen" but leadGenConfig is missing:
renderAd(ad, container, requestId);
// Throws: "leadGenConfig is required for lead_gen format"import { useRef, useEffect } from "react";
import { initialize, requestAd, renderAd } from "@ceedhq/ads-web-sdk";
import type { ResolvedAd } from "@ceedhq/ads-web-sdk";
// Initialize once
initialize("your-app-id");
function ChatMessage({ message }: { message: string }) {
const adRef = useRef<HTMLDivElement>(null);
const [ad, setAd] = useState<ResolvedAd | null>(null);
const [requestId, setRequestId] = useState<string | null>(null);
useEffect(() => {
async function fetchAd() {
const result = await requestAd({
conversationId: "chat-123",
messageId: crypto.randomUUID(),
contextText: message,
});
setAd(result.ad);
setRequestId(result.requestId);
}
fetchAd();
}, [message]);
useEffect(() => {
if (ad && adRef.current) {
adRef.current.innerHTML = "";
renderAd(ad, adRef.current, requestId);
}
}, [ad, requestId]);
return (
<div>
<p>{message}</p>
<div ref={adRef} />
</div>
);
}<div id="ad-container"></div>
<script type="module">
import { initialize, showAd } from "@ceedhq/ads-web-sdk";
initialize("demo-app");
document.getElementById("send-btn").addEventListener("click", async () => {
const message = document.getElementById("input").value;
await showAd({
conversationId: "demo-session",
messageId: Date.now().toString(),
contextText: message,
targetElement: document.getElementById("ad-container"),
});
});
</script>Point the SDK to a local API server:
// Development mode
initialize("test-app", "http://localhost:3000/api");
// Or relative path (same origin)
initialize("test-app", "/api");See the SDK in action:
All ad cards use a dark theme with these CSS classes:
| Class | Description |
|---|---|
.ceed-ads-card |
Base card container |
.ceed-ads-action-card |
Action Card format |
.ceed-ads-lead-gen |
Lead Gen format |
.ceed-ads-static |
Static format |
.ceed-ads-followup |
Followup format |
Cards have a max-width of 460px and use inline styles for consistency.
MIT © Ceed