06 Line

Calls

Place and answer calls with a WebRTC session, without the SDK.

In a browser, use the SDK

The browser SDK does all of this for you, audio included. This page is for those who drive the calls themselves: another language, a server, a native app.

A call needs two things: REST to place, answer and hang up, and a WebRTC session that carries the sound and reports what happens to the call on its data channel.

1. Open a WebRTC session

Create a peer connection with the ice_servers returned with your ephemeral key, add one audio track and a data channel labelled 06line, gather every ICE candidate, then send the offer:

const pc = new RTCPeerConnection({ iceServers });
pc.addTrack(microphoneTrack);
const events = pc.createDataChannel('06line');

await pc.setLocalDescription(await pc.createOffer());
await new Promise((done) => (pc.onicegatheringstatechange = () => pc.iceGatheringState === 'complete' && done()));

const res = await fetch('https://api.06line.com/v1/webrtc/sessions', {
  method: 'POST',
  headers: { Authorization: `Bearer ${ephemeralKey}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ sdp: pc.localDescription.sdp }),
});
const session = await res.json(); // { id: "rtc_…", sdp }
await pc.setRemoteDescription({ type: 'answer', sdp: session.sdp });

There is no trickle ICE: the offer must hold every candidate. Audio is G.711 (PCMU or PCMA), 8 kHz, which every browser supports; keep the offer's default codecs and 06 Line picks it.

Keep the session open as long as the user may call or be called: it carries the audio of one call at a time, call after call. Close it with DELETE /v1/webrtc/sessions/{id} or by closing the peer connection.

2. Listen to the data channel

06 Line sends JSON messages on the 06line channel:

typedataWhen
session.ready{ "lines": Line[] }Right after the channel opens: the lines this session can use
line.statea LineA line went online or offline, or its signal changed (checked every 5 seconds)
call.incominga CallA call is ringing on one of the lines
call.statea CallA call of this session changed state
{ "type": "call.state", "data": { "id": "call_…", "state": "active", "answered_at": "2026-10-21T15:12:11Z" } }

Every open session of a company receives call.incoming: ring everywhere, and the first session to answer takes the call. The channel is for receiving only; actions go through REST.

3. Place a call

curl -X POST https://api.06line.com/v1/calls \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"line_id": "line_…", "to": "+33698765432", "session_id": "rtc_…"}'

The call starts in dialing; call.state then reports ringing, active and ended.

4. Answer a call

On call.incoming, answer with the session that should carry the sound:

curl -X POST https://api.06line.com/v1/calls/call_…/answer \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"session_id": "rtc_…"}'

Without an answer, the call ends with end_reason: "no_answer".

5. Hang up

POST /v1/calls/{id}/hangup ends the call, or declines it while it rings.

end_reasonMeaning
hangupYou hung up
remote_hangupThe other party hung up
no_answerNobody answered
busyThe other party was busy
line_offlineYour line lost the network
failedAny other failure

One call at a time per line and per session: a second one fails with line_busy.

Recordings

Every call is recorded, from the moment it is placed or answered until it ends, as two mono WAV files (PCM 16-bit, 8 kHz), one per speaker:

Both start at the same moment, so they line up: play them together, or transcribe each speaker on its own. They are ready a few seconds after the call ends.

Rather than polling, subscribe a webhook endpoint to call.recorded: it arrives once the files are stored, with the call and a presigned link to each track, valid 7 days and usable without your API key, ready to hand to a transcription service.

In most countries, including France, you must tell the other party that the call is recorded, and why, before recording it. Say it at the start of the call, or play a message.

Native apps

Nothing here is specific to browsers: the same session and REST calls work from any WebRTC stack.

PlatformWebRTC library
iOS (Swift)WebRTC.framework (Google's libwebrtc)
Android (Kotlin, Java, Compose)org.webrtc (libwebrtc)
React Nativereact-native-webrtc
Flutterflutter_webrtc
ServersPion (Go), aiortc (Python), libdatachannel (C++)

The data channel only lives while your app runs. Ringing a phone whose app is closed needs a push notification (PushKit and CallKit on iOS, FCM and ConnectionService on Android); 06 Line does not send those yet.

History

GET /v1/calls lists past and current calls, newest first, filtered by line_id and/or peer (the other party's number), exactly like SMS history.

On this page