The server
Node.js 18 or later:npm install express.
server.mjs
endUserId, so Pearl remembers them and their calls are grouped in the call history. For an anonymous visitor, leave it out.
The page
public/index.html
public/app.js
The audio
An AudioWorklet runs in real time, which gives the pacing the protocol asks for without any timer: it sends one 20 ms frame of the microphone every 20 ms, silence included when the end user is muted, and plays Pearl’s frames once 80 ms are buffered.public/pearl-audio.js
Run it
http://localhost:3000, click Talk to Pearl and allow the microphone. If the call does not start, the browser console shows why, such as a missing variable: add it to callData in server.mjs.
- Browsers allow the microphone only on
https://pages and onlocalhost. - WebCodecs audio, which codes the Opus, is in Chrome and Edge, Firefox 130 and later on desktop, and Safari 26 and later.
AudioContext({ sampleRate: 24000 })makes the browser resample the microphone to 24 kHz. Chrome, Edge and Safari do this; check your other target browsers.- On a laptop, test with headphones: browser echo cancellation does not always cover audio played through Web Audio, and Pearl would hear itself.
Going further
- Phone quality: with
audioFormat: "opus_8000", use 8000 instead of 24000 for the context, the encoder and the decoder, and divide the worklet’sFRAME,CUSHIONandMAX_BEHINDby 3. - Hang-up button:
hangupis answered byended, then the socket closes andonclosereleases the microphone. - Transfers: a
transferevent carries the target set on your Pearl’s Transfer Call node. In a browser there is usually no phone line to pass on, so show the end user the next step instead, or let your server act on it.
Stream WebSocket
Every message, audio format, event and limit.

