Guides
Token Authentication
Secure your production integration. Your API key stays on your server — client apps connect with short-lived tokens.
Why Tokens?
API keys grant full account access. If you put one in a browser or mobile app, anyone can extract it. Instead, your backend generates short-lived tokens (expire in 5 minutes) that can only do one thing: connect a voice or chat session.
The Flow
User taps "Call" → Your app asks YOUR server for a token
Your server → Converse API (with API key) → returns short-lived token
Your server → returns token to app
App → connects to Converse voice/chat with token (no API key needed)
Server: Generate Token (Node.js)
typescript
import { expressTokenHandler } from '@converse/sdk/mobile';
import express from 'express';
const app = express();
app.use(express.json());
app.post('/api/voice-token', expressTokenHandler({
apiKey: process.env.CONVERSE_API_KEY!,
allowedAgentIds: ['agent_xxx'],
authorize: request => isSignedIn(request),
}));Server: Generate Token (Python)
python
from converse import AsyncConverseClient
@app.post("/api/voice-token")
async def voice_token(request: Request):
body = await request.json()
if body["agentId"] not in ALLOWED_AGENT_IDS:
raise HTTPException(403)
async with AsyncConverseClient(api_key=os.environ["CONVERSE_API_KEY"]) as client:
session = await client.calls.create_web_call(body["agentId"])
return {"token": session["token"], "serverUrl": session["server_url"], "callId": session["call_id"]}Client: Use tokenProvider
React (Web)
tsx
import { useConverseVoice, ConverseOrb } from '@converse/sdk/components';
function Agent() {
const { agentState, voiceState, connect, disconnect } = useConverseVoice({
agentId: 'agent_xxx',
tokenProvider: async () => {
const res = await fetch('/api/voice-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ agentId: 'agent_xxx' }),
});
return res.json(); // { token, serverUrl, callId }
},
});
return <ConverseOrb state={agentState} onClick={voiceState === 'idle' ? connect : disconnect} />;
}React Native
tsx
import { useConverseVoice } from '@converse/react-native';
const { connect } = useConverseVoice({
agentId: 'agent_xxx',
tokenProvider: async () => {
const res = await fetch('https://yourapi.com/api/voice-token', {
method: 'POST',
body: JSON.stringify({ agentId: 'agent_xxx' }),
});
return res.json();
},
});iOS (Swift)
swift
let config = ConverseConfig(
voiceTokenProvider: {
let (data, _) = try await URLSession.shared.data(for: tokenRequest)
return try JSONDecoder().decode(VoiceToken.self, from: data)
}
)
let voice = ConverseVoice(config: config)
try await voice.connect(agentId: "agent_xxx")Never use apiKey in production
Never place a secret
sk_* key in client code. Use an origin-restricted publishable key or a server-side tokenProvider.