Ceed Ads TypeScript SDK for integrating contextual ads into chat applications.
npm install @ceedhq/ads-web-sdkEnglish | 日本語
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
- initialize()
- requestAd()
- renderAd()
- showAd()
- Ad Formats
- Action Card
- Lead Gen
- Static
- Followup
- Event Tracking
- TypeScript Types
- Error Handling
- Examples
- Local Development
---
``bash`
npm install @ceedhq/ads-web-sdk
---
`typescript
import { 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.
`typescript`
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:
`typescript
// 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.
`typescript`
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:
`typescript
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});Advertiser: ${ad.advertiserName}
console.log();`
}
---
Renders an ad into the DOM and automatically tracks impressions and clicks.
`typescript`
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:
`typescript
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.
`typescript`
async showAd(options: {
conversationId: string;
messageId: string;
contextText: string;
targetElement: HTMLElement;
userId?: string;
formats?: AdFormat[];
}): Promise
Example:
`typescript`
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 ctaUrl in 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 autocomplete attribute
Required config:
`typescript`
leadGenConfig: {
placeholder: string; // Input placeholder text
submitButtonText: string; // Button label
autocompleteType: "email" | "name" | "tel" | "off";
successMessage: string; // Shown after submit
}
---
Display format — Similar to action_card, for page load targeting.
``
┌─────────────────────────────────┐
│ ● Advertiser Name Ad │
├─────────────────────────────────┤
│ Special Offer │
│ Limited time discount on │
│ selected products. │
│ │
│ ┌─────────────────────────────┐ │
│ │ Shop Now │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────┘
Use case: Banner-style ads for sidebars, page headers, or footers.
Behavior:
- Identical rendering to action_card
- Different targeting logic on backend (keywords, geo, device type)
- Impression tracked on render
- Click tracked on CTA click
Optional config:
`typescript`
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:
`typescript`
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.
`typescript
// 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"],
});
`
`typescript`
// 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:
`typescript`
import {
renderActionCard,
renderLeadGenCard,
renderStaticCard,
renderFollowupCard,
} from "@ceedhq/ads-web-sdk";
---
`typescript`
import type {
ResolvedAd,
AdFormat,
ResolvedLeadGenConfig,
ResolvedFollowupConfig,
StaticConfig,
} from "@ceedhq/ads-web-sdk";
The main ad payload returned from requestAd():
`typescript`
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;
}
`typescript
// 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;
}
`
---
`typescript`
try {
initialize(""); // Empty appId
} catch (error) {
// "CeedAds.initialize: appId is required"
}
`typescript
const { ad, requestId } = await requestAd({...});
if (!ad) {
// No matching ad for this context
// This is normal — ads are not always available
return;
}
`
`typescript`
// If ad.format is "lead_gen" but leadGenConfig is missing:
renderAd(ad, container, requestId);
// Throws: "leadGenConfig is required for lead_gen format"
---
`tsx
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
const [ad, setAd] = useState
const [requestId, setRequestId] = useState
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 (
{message}
$3
`html
`---
Local Development
Point the SDK to a local API server:
`typescript
// Development mode
initialize("test-app", "http://localhost:3000/api");// Or relative path (same origin)
initialize("test-app", "/api");
`$3
See the SDK in action:
- Demo Video
- Demo Source Code
---
Styling
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