Skip to main content

JavaScript SDK

The PerfVoice SDK is a single JavaScript file that handles the entire voice agent integration — WebSocket connection, microphone capture, audio encoding, playback, interruptions, and keepalive. No dependencies, no build step.

Installation

Add the SDK via a script tag:
<script src="https://api.withperf.pro/v1/voice/sdk.js"></script>
Or install via npm (coming soon):
npm install @perf-ai/voice
The SDK exports a PerfVoice class globally (or as a UMD/CommonJS module).

Quick Start

<button id="startBtn">Start</button>
<button id="stopBtn">Stop</button>

<script src="https://api.withperf.pro/v1/voice/sdk.js"></script>
<script>
  const voice = new PerfVoice({
    apiKey: 'YOUR_API_KEY',
    agentId: 'YOUR_AGENT_ID',
  });

  voice.on('transcript', (role, text) => {
    console.log(role + ': ' + text);
  });

  voice.on('error', (err) => console.error(err));

  document.getElementById('startBtn').onclick = () => voice.start();
  document.getElementById('stopBtn').onclick = () => voice.stop();
</script>

Constructor

const voice = new PerfVoice(options);
ParameterTypeRequiredDefaultDescription
apiKeystringYesYour project API key (pk_live_...)
agentIdstringYesVoice agent ID from the dashboard
wsUrlstringNowss://api.withperf.pro/v1/voice/conversationWebSocket endpoint URL

Methods

voice.start()

Start a voice conversation. Requests microphone permission, opens a WebSocket connection, and begins streaming audio.
voice.start()
  .then(() => console.log('Conversation started'))
  .catch((err) => console.error('Failed to start:', err));
Returns a Promise<void> that resolves when the connection is established and the agent is ready. Rejects if microphone access is denied or the WebSocket connection fails.
Note: Browsers require a user gesture (click/tap) before allowing microphone access. Always call start() from a button click handler.

voice.stop()

End the conversation immediately. Stops all audio playback, releases the microphone, and closes the WebSocket.
voice.stop();
This method is synchronous and safe to call at any time, even if the conversation hasn’t started.

Events

Subscribe to events with voice.on(event, callback) and unsubscribe with voice.off(event, callback).

transcript

Fired when a transcript is available for either the agent or the user.
voice.on('transcript', (role, text) => {
  // role: 'agent' or 'user'
  console.log(role + ': ' + text);
});
ArgumentTypeDescription
role'agent' | 'user'Who said it
textstringThe transcript text

connected

Fired when the voice session is established and the agent is ready.
voice.on('connected', (conversationId) => {
  console.log('Session:', conversationId);
});
ArgumentTypeDescription
conversationIdstringUnique session identifier

disconnected

Fired when the WebSocket connection closes (either by calling stop() or due to a server-side close).
voice.on('disconnected', (code, reason) => {
  console.log('Disconnected:', code, reason);
});
ArgumentTypeDescription
codenumberWebSocket close code
reasonstringClose reason (may be empty)

status

Fired whenever the connection status changes.
voice.on('status', (status) => {
  // status: 'disconnected', 'connecting', or 'connected'
  updateUI(status);
});
ArgumentTypeDescription
status'disconnected' | 'connecting' | 'connected'Current state

error

Fired when an error occurs (microphone denied, WebSocket failure, etc.).
voice.on('error', (err) => {
  console.error('Voice error:', err.message);
});
ArgumentTypeDescription
errErrorThe error object

interruption

Fired when the user interrupts the agent (starts speaking while the agent is talking). The SDK automatically stops agent audio playback.
voice.on('interruption', () => {
  console.log('User interrupted the agent');
});

message

Fired for any unhandled message type from the server. Useful for debugging.
voice.on('message', (data) => {
  console.log('Raw message:', data);
});

Properties

PropertyTypeDescription
voice.statusstringCurrent status: 'disconnected', 'connecting', or 'connected'
voice.conversationIdstring | nullCurrent session ID (null when disconnected)

Full Example

A complete example with status indicator, transcript display, and error handling:
<!DOCTYPE html>
<html>
<head>
  <title>Voice Agent</title>
  <style>
    .dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
    .dot.disconnected { background: #ef4444; }
    .dot.connecting { background: #eab308; }
    .dot.connected { background: #22c55e; }
  </style>
</head>
<body>
  <span class="dot disconnected" id="dot"></span>
  <span id="statusText">Disconnected</span>
  <br><br>
  <button id="startBtn">Start Conversation</button>
  <button id="stopBtn" disabled>Stop</button>
  <div id="transcript"></div>

  <script src="https://api.withperf.pro/v1/voice/sdk.js"></script>
  <script>
    const voice = new PerfVoice({
      apiKey: 'YOUR_API_KEY',
      agentId: 'YOUR_AGENT_ID',
    });

    voice.on('status', (status) => {
      document.getElementById('dot').className = 'dot ' + status;
      document.getElementById('statusText').textContent =
        status.charAt(0).toUpperCase() + status.slice(1);
      document.getElementById('startBtn').disabled = status !== 'disconnected';
      document.getElementById('stopBtn').disabled = status === 'disconnected';
    });

    voice.on('connected', (id) => {
      document.getElementById('transcript').innerHTML = '';
      addLine('System', 'Connected — session ' + id);
    });

    voice.on('transcript', (role, text) => {
      addLine(role === 'agent' ? 'Agent' : 'You', text);
    });

    voice.on('error', (err) => {
      addLine('Error', err.message);
    });

    document.getElementById('startBtn').onclick = () => voice.start();
    document.getElementById('stopBtn').onclick = () => voice.stop();

    function addLine(label, text) {
      const el = document.getElementById('transcript');
      el.innerHTML += '<p><strong>' + label + ':</strong> ' + text + '</p>';
      el.scrollTop = el.scrollHeight;
    }
  </script>
</body>
</html>

React Integration

import { useEffect, useRef, useState } from 'react';

function VoiceAgent({ apiKey, agentId }) {
  const voiceRef = useRef(null);
  const [status, setStatus] = useState('disconnected');
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const voice = new PerfVoice({ apiKey, agentId });
    voiceRef.current = voice;

    voice.on('status', setStatus);
    voice.on('transcript', (role, text) => {
      setMessages((prev) => [...prev, { role, text }]);
    });
    voice.on('error', (err) => console.error(err));

    return () => voice.stop();
  }, [apiKey, agentId]);

  return (
    <div>
      <p>Status: {status}</p>
      <button
        onClick={() => voiceRef.current?.start()}
        disabled={status !== 'disconnected'}
      >
        Start
      </button>
      <button
        onClick={() => voiceRef.current?.stop()}
        disabled={status === 'disconnected'}
      >
        Stop
      </button>
      <div>
        {messages.map((m, i) => (
          <p key={i}><strong>{m.role}:</strong> {m.text}</p>
        ))}
      </div>
    </div>
  );
}

Error Handling

ErrorCauseResolution
NotAllowedErrorMicrophone permission deniedPrompt user to allow microphone in browser settings
WebSocket connection failedNetwork issue or invalid credentialsCheck API key and network connectivity
Already connecting / Already connectedstart() called while already activeCheck voice.status before calling start()

Browser Support

The SDK requires:
  • navigator.mediaDevices.getUserMedia (microphone access)
  • AudioContext (audio playback)
  • WebSocket (real-time communication)
Supported in all modern browsers: Chrome 60+, Firefox 55+, Safari 14+, Edge 79+.