Websites and apps
Add live voice conversations between your users and a Synthflow agent, right inside your own website or app. You start the call with one API request, open the WebSocket URL it returns, and stream audio both ways over that single connection. Synthflow handles the call setup and telephony on the server, so your client only needs a WebSocket and an audio pipeline.
Use this when you want to build your own voice experience instead of using the voice widget. For example, you might add a custom call button to your product, let users speak to an agent from a mobile app, or connect an internal tool that listens to active calls.
The integration has three parts:
- Create the call. Your backend asks Synthflow to start a voice session.
- Connect your app. Your client opens the secure WebSocket URL returned by Synthflow.
- Send and receive audio. Your app sends the user’s voice and plays back the agent’s response in real time.
This guide focuses on the end-to-end integration flow for websites and apps, including codec behavior, RTP frame handling, and the client responsibilities for streaming audio.
Prerequisites
Before you start, make sure you have the pieces that create the call, protect your credentials, and handle audio in your app.
- Voice agent. Create or choose a voice agent in your workspace, then copy its model ID from the agent editor. You will use this as the
modelIdwhen you start the call. - Backend endpoint. Your app needs a server-side route that can call Synthflow with your API key. The browser or mobile app should call your backend, not Synthflow directly. See Authentication for the recommended pattern.
- Audio handling. Your client needs to capture microphone audio, play back the agent’s response, and encode or decode RTP for the selected codec. Use
opusunless you specifically needpcmaorpcmufor G.711 compatibility.
Step 1. Start a call and get the WebSocket URL
Start by creating a voice session from your backend.
The response includes a callId and a short-lived wsMediaUrl. Keep callId on your backend so you can look up the call later, and pass wsMediaUrl to your client for Step 2.
You do not need a separate token request or any browser-side call setup.
Step 2. Connect your client to the call
Use the wsMediaUrl from Step 1 to open a WebSocket connection. Set binaryType = "arraybuffer" so your client can receive audio frames as binary data.
When the connection opens, Synthflow sends a single JSON setup message called call_info. It tells your client which codec, sample rate, and RTP payload type to use for the audio stream.
Use these values to configure your RTP encoder. After this setup message, there are no more JSON messages on this socket.
The call_info message is the only text frame on this WebSocket. Treat every later message as binary media.
Step 3. Send and receive audio
After call_info, the WebSocket is ready for audio. From this point on, your client sends microphone audio to Synthflow and receives the call audio it should play back to the user.
- Client to server. Send complete RTP packets (12-byte header plus payload) as binary WebSocket frames.
- Server to client. Receive mixed audio from the call room (agent plus other participants) as binary RTP frames.
There are no additional JSON status messages on this socket. Treat every binary frame as a complete RTP packet for the codec advertised in call_info.
Example browser client
This example shows the client-side flow in a browser: open the WebSocket URL from your backend, wait for call_info, play incoming audio frames, and start sending microphone audio. It uses the browser WebSocket and getUserMedia APIs, and assumes your backend already returned wsMediaUrl.
The example leaves playRtpPacket and startRtpSender to your RTP and codec stack.
End the call
Close the WebSocket when the client is done with the media stream. Keep the callId from Step 1 so your backend can look up the call later.
Security
Before you ship, check the parts of the flow that expose credentials or call access:
- Keep your API key on the backend. Never expose Synthflow API keys in browser code, mobile apps, logs, or analytics tools. See Authentication for the standard API key pattern.
- Protect your own start-call route. The endpoint that calls Synthflow should require your app’s user authentication, so only authorized users can start calls from your account.
- Treat
wsMediaUrllike a temporary secret. Anyone with the URL can join that media connection. Send it over HTTPS, do not store it long term, and request a fresh URL for each session. - Keep
callIdserver-side. Store it with the user’s session or call record so your backend can look up the call later.
Attach to an existing call
If a call is already running (started via API, phone, or portal), you can join its media leg without starting a new call. Pass the running call’s ID as roomId and receive a fresh wsMediaUrl.
You can open multiple WebSocket legs on the same call (for example, listen-only monitoring).
Troubleshooting
Legacy websocket
If you previously used the legacy PCM websocket at widget.synthflow.ai, use this table to map the old flow to WebSocket media. The legacy websocket was deprecated on June 15, 2026. Widget embeds are not affected.
For most integrations, the main change is that your backend starts the call and your client streams RTP over the returned WebSocket URL.
FAQ
Do I need this if I just want a voice widget on my site?
No. If you only need a drop-in website widget, use the voice widget, which requires no custom WebSocket code. This guide is for programmatic integrations where you control the audio pipeline in a browser, mobile, or headless client.
Which codec should I use?
Use opus (the default) for the best quality on browser and app clients. Choose pcma or pcmu when you need G.711 compatibility with telephony systems. Always configure your encoder from the call_info values (codec, payload_type, sample_rate) rather than hardcoding them.
Is there any client-side signaling to handle?
No. Synthflow provisions the room on the server, so there is no trickle ICE, SDP exchange, or status_client_ready handshake. Your client opens wsMediaUrl, reads the single call_info frame, and then streams binary RTP.
Can I join a call that is already running?
Yes. Pass the running call’s ID as roomId to receive a fresh wsMediaUrl without starting a new call. You can open multiple WebSocket legs on the same call, for example a listen-only monitoring leg. See Attach to an existing call.
Why did my WebSocket close immediately?
The wsMediaUrl is short-lived and region-scoped. If it expired or was requested for the wrong region, request a fresh URL from your backend and reconnect. See Troubleshooting for more symptoms.