Call with the SDK
Place and answer calls from a web page with the browser SDK, in a few lines.
Complete example
06line/example-softphone is a working softphone in Next.js built on this
page: clone it, put your API key in .env.local and run it.
@06line/sdk-browser does the plumbing of a call in a browser: the WebRTC session and its reconnections, the ephemeral keys,
the microphone, the choice of devices, the volumes. You write the interface. If you prefer to drive the calls yourself,
everything it does is described in Call with the API, which follows the same steps.
Install
npm install @06line/sdk-browserGet a key
The browser never sees your API key. Add a route to your server that asks 06 Line for an ephemeral key and returns it as is, after checking that your user may call:
export async function POST() {
const res = await fetch('https://api.06line.com/v1/ephemeral-keys', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.SIXLINE_API_KEY}` },
});
return new Response(res.body, { status: res.status, headers: { 'Content-Type': 'application/json' } });
}The SDK calls this route when it starts, then again about a minute before each 15-minute key expires.
Create the phone
import { createPhone } from '@06line/sdk-browser';
const phone = createPhone({
getEphemeralKey: () => fetch('/api/06line-key', { method: 'POST' }).then((r) => r.json()),
});
phone.on('state', (state) => (statusEl.textContent = state)); // connecting, connected, reconnecting, closedLet the user pick devices
const { inputs, outputs } = await phone.devices();
fillSelect(micSelect, inputs);
fillSelect(speakerSelect, outputs);
speakerSelect.hidden = !phone.canChooseOutput; // no setSinkId (some Safari versions): sound follows the system outputDevice names stay empty until the user has granted microphone access once; phone.testMicrophone() is a good moment
to ask, and lets them hear themselves before a real call.
Place a call
Browsers only let a page use the microphone and play sound after a user action, so start calls from a click. Each call takes its own microphone, speaker and volumes:
callButton.onclick = async () => {
const call = await phone.call({
to: numberInput.value,
input: micSelect.value,
output: phone.canChooseOutput ? speakerSelect.value : undefined,
inputVolume: Number(micVolume.value), // 0 to 2, 1 = unchanged
outputVolume: Number(speakerVolume.value),
});
call.on('state', (state) => (callStateEl.textContent = state)); // dialing, ringing, active, ended
call.on('level', ({ input, output }) => drawMeters(input, output));
call.on('ended', (reason) => console.log('call ended:', reason));
// everything can change mid-call
micSelect.onchange = () => call.setInput(micSelect.value);
speakerSelect.onchange = () => call.setOutput(speakerSelect.value);
micVolume.oninput = () => call.setInputVolume(Number(micVolume.value));
speakerVolume.oninput = () => call.setOutputVolume(Number(speakerVolume.value));
hangupButton.onclick = () => call.hangup();
muteButton.onclick = () => call.mute(!call.muted);
};With several lines, pass lineId; phone.lines lists them with their state. Without input or output, the call
uses the devices of the previous call, else the system defaults; a remembered device that has been unplugged is
forgotten. The microphone opens before the call is placed, so a refused microphone places no call.
A call does not survive a lost connection: if the network drops for long enough that the WebRTC session fails, the call ends once the phone has reconnected (see reconnection).
Answer incoming calls
answer() takes the same audio options:
Every open page of your company rings; the first to answer takes the call, and on the others it ends with
answered_elsewhere.
phone.on('incoming', (call) => {
showRinging(call.from);
answerButton.onclick = () =>
call.answer({ input: micSelect.value, output: phone.canChooseOutput ? speakerSelect.value : undefined });
declineButton.onclick = () => call.hangup();
call.on('ended', hideRinging);
});SMS
The SDK is for calls. Incoming SMS and delivery reports reach your server through webhooks, and sending is a REST call your server makes with its API key.
Handling errors
Every failure is a PhoneError with a code. The ones a softphone should handle:
import { PhoneError } from '@06line/sdk-browser';
try {
await phone.call({ to });
} catch (err) {
if (err instanceof PhoneError && err.code === 'microphone_denied') return showMicHelp();
if (err instanceof PhoneError && err.code === 'line_busy') return showMessage('Already on a call');
throw err;
}The full list is in the SDK reference.