External Platforms
OpenAI Realtime API
Integrate Trulience avatars with OpenAI's Realtime voice API using WebRTCOverview
OpenAI’s Realtime API provides low-latency voice-to-voice conversations using WebRTC.
Working example: OpenAI integration example
How It Works
- OpenAI Realtime API handles speech recognition, conversation logic, and voice synthesis
- Trulience renders the avatar and synchronizes lip movements with OpenAI’s audio output
- Audio is delivered via standard WebRTC RTCPeerConnection- no SDK required
Prerequisites
- OpenAI API key with Realtime API access
- A Trulience avatar configured for External Voice Platforms
- Basic knowledge of React and WebRTC
Dashboard Configuration
- Open your avatar’s settings in the Trulience dashboard
- Navigate to the BRAIN tab
- Select ‘3rd Party AI’ mode
- Set ‘Service provider or framework’ to ‘External Voice Platforms’
This configuration disables Trulience’s built-in STT, LLM, and TTS, allowing OpenAI to handle these components.
Integration Steps
1. Install Dependencies
npm install @trulience/react-sdk2. Get Ephemeral Key
OpenAI Realtime API requires an ephemeral key from your backend. Create an API endpoint:
// app/api/token/route.ts
export async function GET() {
  const response = await fetch('https://api.openai.com/v1/realtime/sessions', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      model: 'gpt-4o-realtime-preview-2024-12-17',
      voice: 'verse'
    })
  });
  const data = await response.json();
  return Response.json(data);
}Then fetch it from the frontend:
const tokenResponse = await fetch('/api/token');
const data = await tokenResponse.json();
const ephemeralKey = data.client_secret.value;3. Create RTCPeerConnection
const pc = new RTCPeerConnection();
// Add microphone input
const micStream = await navigator.mediaDevices.getUserMedia({ audio: true });
pc.addTrack(micStream.getTracks()[0]);4. Capture Remote Audio Track
This is where the Trulience integration happens:
// Listen for OpenAI's audio track
pc.ontrack = (event) => {
  const remoteStream = event.streams[0];
  // Route audio to Trulience avatar
  trulienceRef.current.setMediaStream(remoteStream);
  trulienceRef.current.getTrulienceObject().setSpeakerEnabled(true);
  // Mute any auto-created audio elements to prevent double audio
  const audioElement = document.querySelector('audio');
  if (audioElement) audioElement.muted = true;
};5. Exchange SDP with OpenAI
// Create offer
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
// Send offer to OpenAI
const sdpResponse = await fetch(
  'https://api.openai.com/v1/realtime/calls?model=gpt-4o-realtime-preview-2024-12-17',
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${ephemeralKey}`,
      'Content-Type': 'application/sdp'
    },
    body: offer.sdp
  }
);
// Set remote description
const answer = {
  type: 'answer',
  sdp: await sdpResponse.text()
};
await pc.setRemoteDescription(answer);Complete React Example
Here’s a full working implementation:
import React, { useRef, useState } from 'react';
import { TrulienceAvatar } from '@trulience/react-sdk';
function OpenAIRealtimeIntegration() {
  const trulienceRef = useRef(null);
  const audioRef = useRef(null);
  const [pc, setPc] = useState(null);
  const [connected, setConnected] = useState(false);
  const startSession = async () => {
    try {
      // Get ephemeral key from backend
      const tokenResponse = await fetch('/api/token');
      const data = await tokenResponse.json();
      const ephemeralKey = data?.client_secret?.value;
      if (!ephemeralKey) {
        throw new Error('Failed to get ephemeral key');
      }
      // Create peer connection
      const newPc = new RTCPeerConnection();
      // Set up audio playback (muted to avoid duplicate audio)
      if (audioRef.current) {
        audioRef.current.autoplay = true;
        audioRef.current.muted = true;
      }
      // Capture remote audio track
      newPc.ontrack = (e) => {
        if (audioRef.current) {
          audioRef.current.srcObject = e.streams[0];
        }
        // Route to Trulience
        if (trulienceRef.current) {
          trulienceRef.current.setMediaStream(e.streams[0]);
          trulienceRef.current.getTrulienceObject().setSpeakerEnabled(true);
        }
      };
      // Add microphone input
      const micStream = await navigator.mediaDevices.getUserMedia({ audio: true });
      newPc.addTrack(micStream.getTracks()[0]);
      // Create offer
      const offer = await newPc.createOffer();
      await newPc.setLocalDescription(offer);
      // Send offer to OpenAI Realtime API
      const sdpResponse = await fetch(
        'https://api.openai.com/v1/realtime/calls?model=gpt-4o-realtime-preview-2024-12-17',
        {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${ephemeralKey}`,
            'Content-Type': 'application/sdp'
          },
          body: offer.sdp
        }
      );
      const answer = {
        type: 'answer',
        sdp: await sdpResponse.text()
      };
      await newPc.setRemoteDescription(answer);
      setPc(newPc);
      setConnected(true);
    } catch (error) {
      console.error('Failed to start session:', error);
    }
  };
  const endSession = () => {
    if (pc) {
      pc.getSenders().forEach(sender => sender.track?.stop());
      pc.close();
      setPc(null);
      setConnected(false);
    }
  };
  return (
    <div className="relative min-h-screen">
      <audio ref={audioRef} style={{ display: 'none' }} />
      <div className="absolute inset-0">
        <TrulienceAvatar
          ref={trulienceRef}
          url={process.env.NEXT_PUBLIC_TRULIENCE_SDK_URL}
          avatarId="your-avatar-id"
          token="your-trulience-token"
          width="100%"
          height="100%"
        />
      </div>
      <button
        onClick={connected ? endSession : startSession}
        className={`absolute bottom-6 left-1/2 -translate-x-1/2 px-6 py-3 rounded-lg text-white font-semibold ${
          connected ? 'bg-red-600 hover:bg-red-700' : 'bg-blue-600 hover:bg-blue-700'
        }`}
      >
        {connected ? 'Disconnect' : 'Connect'}
      </button>
    </div>
  );
}
export default OpenAIRealtimeIntegration;Key Integration Points
Preventing Double Audio
OpenAI’s audio arrives via WebRTC track. To prevent hearing both OpenAI’s audio and the avatar’s audio:
- Create a muted <audio>element to receive the WebRTC stream
- Route the same stream to Trulience via setMediaStream()
- Enable the avatar’s speaker with setSpeakerEnabled(true)
// Muted audio element prevents browser from playing directly
<audio ref={audioRef} muted autoplay style={{ display: 'none' }} />
// In ontrack handler
audioRef.current.srcObject = e.streams[0];  // Muted playback
trulienceRef.current.setMediaStream(e.streams[0]);  // Avatar playback
trulienceRef.current.getTrulienceObject().setSpeakerEnabled(true);Using Data Channels
You can also send events to OpenAI via a data channel:
const channel = pc.createDataChannel('oai-events');
channel.addEventListener('message', (e) => {
  const event = JSON.parse(e.data);
  console.log('OpenAI event:', event);
});
channel.onopen = () => {
  // Send initial instruction
  channel.send(JSON.stringify({
    type: 'response.create',
    response: {
      instructions: 'Introduce yourself as a Trulience AI avatar.'
    }
  }));
};Troubleshooting
Issue: No audio from avatar
- Verify setSpeakerEnabled(true)is called
- Check that remote stream has audio tracks: e.streams[0].getAudioTracks().length > 0
- Ensure the audio element is muted (not the stream)
Issue: Connection fails
- Ensure ephemeral key is valid and not expired
- Check browser console for ICE connection errors
- Verify HTTPS is used (WebRTC requires secure context)
Issue: Avatar not lip-syncing
- Check that setMediaStream()is called with a valid MediaStream
- Ensure the avatar is configured for ‘External Voice Platforms’ in the dashboard
- Verify the audio track is active: stream.getAudioTracks()[0].enabled === true
Example Code Repository
See our OpenAI integration example for a complete working implementation with error handling, state management, and additional features.
Next Steps
- Review the Integration Guide for general audio routing patterns
- Explore OpenAI’s Realtime API documentation
- Check out our Javascript SDK documentation for more avatar controls