Integration Docs
Step-by-step guide to embed @streamoji/aitwin (^0.2.3) — a React component that renders an AI twin face with TTS lipsync.
Prerequisites
You need a React 18 or 19 project with Node.js and npm (or pnpm/yarn). The package renders a canvas-based AI twin face with TTS lipsync — it runs in the browser only.
Peer dependencies: react and react-dom. If you use Next.js or enforce a strict Content Security Policy, see Integration notes below.
Install the package
Add @streamoji/aitwin and its peer dependencies to your project:
npm install @streamoji/aitwinRender your twin
Provide id (cloud twin slug, e.g. olivia). Use stable useCallback handlers for onReady and onError:
function TwinDemo() {
const twinRef = useRef<AiTwinHandle>(null);
return (
<AiTwin
ref={twinRef}
id="olivia"
onReady={() => console.log("face ready")}
onStatusChange={(status) => console.log("status", status)}
onError={(message) => console.error(message)}
/>
);
}Speak on demand
Control speech imperatively via the ref handle. Call speakText with any string; use stop() to interrupt playback:
<button
type="button"
onClick={() => void twinRef.current?.speakText("Hi, how are you?")}
>
Speak
</button>
<button
type="button"
onClick={() => twinRef.current?.stop()}
>
Stop
</button>Production auth (Proxy)
For production TTS and encrypted twin assets, authenticate each session with an authToken — a short-lived Bearer credential you pass via the authToken prop. It authorizes the twin to stream TTS without ever exposing your long-lived secret in the browser.
Generate tokens from your backend by calling getAiTwinAuthToken with your Client-Id and Client-Secret in request headers (not the body). Create and view these credentials on the API Keys page in your aitwin dashboard. Pass userId and userName so usage is attributed to the right end customer in your account.
Your backend calls getAiTwinAuthToken, then your frontend receives only the returned authToken — never the Client Secret.
curl -X POST "https://us-central1-streamoji-265f4.cloudfunctions.net/getAiTwinAuthToken" \
-H "Content-Type: application/json" \
-H "Client-Id: YOUR_CLIENT_ID" \
-H "Client-Secret: YOUR_64_CHAR_API_KEY" \
-d '{
"userId": "end-user-123",
"userName": "Jane Doe"
}'// Backend only — never run this in the browser
const CLIENT_ID = process.env.AITWIN_CLIENT_ID;
const CLIENT_SECRET = process.env.AITWIN_CLIENT_SECRET;
async function getAiTwinAuthToken({
userId,
userName,
maxAvatarCreations,
maxCreditsUtilization,
expiresIn,
}) {
const body = { userId, userName };
if (typeof maxAvatarCreations === "number") {
body.maxAvatarCreations = maxAvatarCreations;
}
if (typeof maxCreditsUtilization === "number") {
body.maxCreditsUtilization = maxCreditsUtilization;
}
if (typeof expiresIn === "number") {
body.expiresIn = expiresIn; // seconds; -1 = no expiry; omit = 30 min default
}
const response = await fetch(
"https://us-central1-streamoji-265f4.cloudfunctions.net/getAiTwinAuthToken",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"Client-Id": CLIENT_ID,
"Client-Secret": CLIENT_SECRET,
},
body: JSON.stringify(body),
},
);
const data = await response.json();
if (!response.ok || !data.success) {
throw new Error(data.error ?? "Auth token generation failed");
}
return data.authToken; // e.g. "client_eyJ..."
}// Frontend — pass the token from your backend API
<AiTwin
ref={twinRef}
id="olivia"
authToken={authToken}
/>Integration notes
Framework and deployment guidance that is not part of the core install flow.
Next.js
Skip this if you are not using Next.js. AiTwin is browser-only (canvas and related APIs) and must not be server-rendered.
Add the package to transpilePackages in next.config.ts, then import AiTwin with next/dynamic and ssr: false in a client component. The viseme worker is loaded from the Streamoji CDN at runtime — you do not need to copy worker files into your app.
// next.config.ts
const nextConfig = {
transpilePackages: ["@streamoji/aitwin"],
};
export default nextConfig;"use client";
import dynamic from "next/dynamic";
import { useRef } from "react";
import type { AiTwinHandle } from "@streamoji/aitwin";
const AiTwin = dynamic(
() => import("@streamoji/aitwin").then((m) => m.AiTwin),
{ ssr: false },
);Content Security Policy (CSP)
If your site sends a Content-Security-Policy header (or meta tag), allow the AiTwin API, CDN, and WebSocket endpoints below. The SDK fetches encrypted face assets and the lipsync worker from the CDN, streams TTS from the API, and uses blob: URLs for the worker script.
Voice sessions (connect()) also require wss:// on the API host. Backend-only calls such as getAiTwinAuthToken run on your server and are not part of the browser CSP.
API (connect-src, media-src)
- https://ai.streamoji.com
- wss://ai.streamoji.com
CDN — face assets, thumbnails, worker script (connect-src, img-src, worker-src)
- https://pub-607ad1fc22e2400eb57d17240aab857c.r2.dev
Dashboard & docs (optional — links only, not required for embed)
- https://aitwin.me
# Example CSP additions (merge with your existing policy)
connect-src 'self' https://ai.streamoji.com wss://ai.streamoji.com https://pub-607ad1fc22e2400eb57d17240aab857c.r2.dev;
img-src 'self' https://pub-607ad1fc22e2400eb57d17240aab857c.r2.dev data: blob:;
media-src 'self' blob: https://ai.streamoji.com;
worker-src blob: https://pub-607ad1fc22e2400eb57d17240aab857c.r2.dev;
script-src 'self' blob:;Props reference
| Name | Description |
|---|---|
| id | Twin slug for cloud lookup (e.g. olivia). |
| authToken | Short-lived Bearer auth token for TTS and encrypted assets. Generate via getAiTwinAuthToken on your backend. |
| ttsEngineId | TTS engine override (default Cartesia). |
| voiceId | Override the default TTS voice. |
| speakingRate | Default speaking rate (default 0.85). |
| onReady | Called when face assets are loaded and canvas is ready. |
| onStatusChange | TTS status: idle, loading, speaking, done, error. |
| onError | Load or runtime errors. |
Ref methods
| Name | Description |
|---|---|
| speakText(text, options?) | Run TTS + lipsync. Optional per-call tts / voiceId overrides. |
| stop() | Stop playback and return toward idle. |
| isReady() | Whether face assets are loaded. |