@phonely-ai/web
v0.0.8
Published
Phonely Web SDK for AI voice calls
Readme
@phonely-ai/web
A browser SDK for adding Phonely AI voice calls to web applications. It handles call setup, microphone control, call events, audio level updates, and optional live transcript updates through a small TypeScript API.
Installation
npm install @phonely-ai/webMigration Note
Version 0.0.8 requires authenticated web call starts. Browser code must provide either webCallStartEndpoint or createWebCallStart; the SDK no longer accepts or uses a Phonely API key in the browser.
Your Phonely API key must stay on your server. Your server creates the web call session and returns the short-lived session bundle to the browser.
Quick Start
import PhonelySDK, {PhonelyEvent} from '@phonely-ai/web';
// Initialize the SDK with your own app server's session endpoint.
// Do not expose your Phonely API key in browser code.
const phonely = new PhonelySDK({
webCallStartEndpoint: '/api/web-call/start'
});
// Start a call
const callId = await phonely.call({
agentId: 'your_agent_id'
});
// Listen for events
phonely.on(PhonelyEvent.CALL_JOINED, () => {
console.log('Call joined successfully');
});
// End the call
await phonely.endCall();React Hook Example
import PhonelySDK, { PhonelyEvent } from '@phonely-ai/web';
function usePhonely(agentId: string) {
const [isCalling, setIsCalling] = useState(false);
const phonelyRef = useRef<PhonelySDK | null>(null);
useEffect(() => {
// Initialize SDK
phonelyRef.current = new PhonelySDK({
webCallStartEndpoint: '/api/web-call/start'
});
// Set up event listeners
phonelyRef.current.on(PhonelyEvent.CALL_JOINED, () => {
setIsCalling(true);
});
return () => {
if (phonelyRef.current) {
phonelyRef.current.endCall().catch(console.error);
}
};
}, []);
const startCall = async () => {
try {
await phonelyRef.current?.call({ agentId });
} catch (error) {
console.error('Call failed:', error);
}
};
const endCall = async () => {
try {
await phonelyRef.current?.endCall();
setIsCalling(false);
} catch (error) {
console.error('End call failed:', error);
}
};
return { isCalling, startCall, endCall };
}Live Transcript
// Enable transcript in config
const phonely = new PhonelySDK({
webCallStartEndpoint: '/api/web-call/start',
transcript: {
enabled: true,
autoStart: true, // Starts automatically when call joins
pollingInterval: 2000, // Poll every 2 seconds
includeRoles: ['user', 'assistant'] // Filter by roles
}
});
// Listen for transcript messages
phonely.on(PhonelyEvent.TRANSCRIPT_MESSAGE, (event) => {
const { message, allMessages } = event;
console.log('New message:', message.content);
console.log('All messages:', allMessages);
});
// Manual control
phonely.startTranscript(); // Start transcript
phonely.stopTranscript(); // Stop transcript
const messages = phonely.getTranscript(); // Get all messages
phonely.clearTranscript(); // Clear in-memory transcript messagesAPI Reference
Authenticated Web Call Starts
Production browser integrations must create calls through your own server-side endpoint. webCallStartEndpoint is a route in your app, not a Phonely-hosted URL; name it however you prefer.
The browser SDK sends this request to your webCallStartEndpoint:
{
agentId: string;
metadata?: Record<string, any>;
}Your server should authenticate the browser user, check that they can access the requested agent, then call Phonely server-side. Keep the customer API key on your server:
POST https://db.phonely.ai/api/agents/{agent_id}/web-call-session
X-Authorization: <customer API key>
Content-Type: application/json{
metadata?: Record<string, any>;
}Your server returns the Phonely response bundle to the browser:
type WebCallStartResponse = {
callId: string;
webCallUrl: string; // URL used by the SDK to connect the call
meetingToken: string; // short-lived token for this web call session
phonelyToken: string; // short-lived token for Phonely transcript access
expiresAt?: string; // ISO timestamp for token expiration
transcriptUrl?: string; // fully-qualified transcript endpoint
};The SDK uses this bundle to connect the call. If live transcript is enabled, it reads transcript updates from transcriptUrl using Authorization: Bearer <phonelyToken>. If transcriptUrl is omitted, configure transcriptBaseUrl and the SDK will read from ${transcriptBaseUrl}/api/calls/{callId}/transcript.
Do not send your Phonely API key to the browser. Only return the short-lived fields shown above.
Example server-side proxy:
const PHONELY_API_BASE_URL = 'https://db.phonely.ai';
app.post('/api/web-call/start', async (req, res) => {
const { agentId, metadata } = req.body;
const response = await fetch(
`${PHONELY_API_BASE_URL}/api/agents/${encodeURIComponent(agentId)}/web-call-session`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Authorization': process.env.PHONELY_API_KEY!
},
body: JSON.stringify(metadata ? { metadata } : {})
}
);
res.status(response.status).json(await response.json());
});If Phonely gives you an environment-specific API base URL, use that value instead of https://db.phonely.ai.
For custom auth flows, provide a callback instead of an endpoint:
const phonely = new PhonelySDK({
createWebCallStart: async ({ agentId, metadata }) => {
const response = await fetch('/api/web-call/start', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ agentId, metadata })
});
return response.json();
}
});PhonelySDK Configuration
interface PhonelyConfig {
webCallStartEndpoint?: string;
webCallStartHeaders?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
createWebCallStart?: (options: CallOptions) => Promise<WebCallStartResponse>;
transcriptBaseUrl?: string;
transcript?: {
enabled?: boolean; // Enable transcript functionality
autoStart?: boolean; // Auto-start when call joins (default: true)
pollingInterval?: number; // Polling interval in ms (default: 2000)
maxRetries?: number; // Max retry attempts (default: 5)
includeRoles?: string[]; // Filter by roles (default: ['user', 'assistant'])
};
}Methods
call(options: CallOptions): Promise<string>
Initiates a call with an AI assistant.
options.agentId: ID of the agent to calloptions.metadata?: Additional metadata for the call- Returns: Promise resolving to the call ID
Runtime Variables (Web SDK)
If your workflow uses Runtime Variables, you must pass values at call start using metadata.callOverrides.liveVariables.
How it works:
- In the workflow editor, create a Runtime Variable and copy its Runtime Variable ID.
- Pass that ID as
uuidinliveVariables. - The
valueis the runtime value you want to inject for this call.
Example:
await phonely.call({
agentId: 'nPhns6zaVgCyMZQ8i9ke',
metadata: {
callOverrides: {
liveVariables: [
{
uuid: '7fc58831-f651-4084-ab5e-70c7c695d8ca', // Runtime Variable ID from workflow editor
value: 'You are testing the runtime variable now',
},
],
},
},
});Notes:
uuidmust match the Runtime Variable ID exactly.- This is currently supported for Web SDK calls.
endCall(): Promise<void>
Ends the current call and stops live transcript updates. The SDK keeps the last transcript messages in memory until the next call starts or clearTranscript() is called.
setAudioEnabled(enabled: boolean): Promise<void>
Enables or disables the local audio.
getParticipants(): Participant[]
Gets all participants in the call.
sendMessage(content: string): Promise<void>
Sends a message to the call.
startTranscript(): void
Starts live transcript polling.
stopTranscript(): void
Stops live transcript polling.
getTranscript(): TranscriptMessage[]
Gets all transcript messages.
clearTranscript(): void
Clears the SDK's in-memory transcript messages. This does not delete backend transcript data.
isTranscriptActive(): boolean
Checks if transcript is currently active.
Events
enum PhonelyEvent {
CALL_JOINED = 'call-joined',
CALL_LEFT = 'call-left',
PARTICIPANT_JOINED = 'participant-joined',
PARTICIPANT_LEFT = 'participant-left',
PARTICIPANT_UPDATED = 'participant-updated',
MESSAGE = 'message',
VOLUME_LEVEL = 'volume-level',
SPEECH_START = 'speech-start',
SPEECH_END = 'speech-end',
CALL_START_PROGRESS = 'call-start-progress',
TRANSCRIPT_STARTED = 'transcript-started',
TRANSCRIPT_STOPPED = 'transcript-stopped',
TRANSCRIPT_MESSAGE = 'transcript-message',
TRANSCRIPT_ERROR = 'transcript-error',
ERROR = 'error'
}License
MIT
