Skip to content

WebSockets

L'API Voki utilise Phoenix Channels pour la communication en temps réel. Tous les canaux nécessitent une authentification via token JWT.

Connexion

URL de Connexion

wss://voki.avanter.com.br/socket/websocket?token={jwt_access_token}

Exemple avec JavaScript

javascript
import { Socket } from "phoenix"

const socket = new Socket("wss://voki.avanter.com.br/socket/websocket", {
  params: { token: accessToken }
})

socket.connect()

// Rejoindre un canal
const channel = socket.channel("queue:department_id", {})

channel.join()
  .receive("ok", (resp) => console.log("Connecté :", resp))
  .receive("error", (resp) => console.log("Erreur :", resp))

Canaux Disponibles

CanalDescriptionÉvénements
call:{call_id}Signalisation d'appelsignal, ice_candidate, join, leave
queue:{department_id}File d'attentecustomer_joined, customer_left, call_assigned
presence:{department_id}Présence des agentspresence_state, presence_diff
notification:{user_id}Notifications personnellesnew_notification, appointment_reminder

Canal : call:

Canal pour la signalisation WebRTC et le contrôle de l'appel vidéo.

Événements Envoyés par le Client

signal

Envoie une offre/réponse SDP pour la signalisation WebRTC.

javascript
channel.push("signal", {
  type: "offer",  // "offer" | "answer"
  sdp: "v=0\r\no=- ..."
})

ice_candidate

Envoie un candidat ICE pour la négociation de connectivité.

javascript
channel.push("ice_candidate", {
  candidate: "candidate:1234567890 1 udp ...",
  sdpMid: "0",
  sdpMLineIndex: 0
})

mute

Active/désactive le mode muet audio/vidéo.

javascript
channel.push("mute", { type: "audio", muted: true })
channel.push("mute", { type: "video", muted: true })

Événements Reçus par le Client

signal

Reçoit une offre/réponse SDP du serveur SFU.

javascript
channel.on("signal", (payload) => {
  // payload: { type: "answer", sdp: "v=0\r\n..." }
  peerConnection.setRemoteDescription(new RTCSessionDescription(payload))
})

ice_candidate

Reçoit un candidat ICE du serveur.

javascript
channel.on("ice_candidate", (payload) => {
  peerConnection.addIceCandidate(new RTCIceCandidate(payload))
})

participant_joined

Notifie qu'un participant a rejoint l'appel.

javascript
channel.on("participant_joined", (payload) => {
  // payload: { user_id: "...", name: "João", role: "attendant" }
})

participant_left

Notifie qu'un participant a quitté l'appel.

javascript
channel.on("participant_left", (payload) => {
  // payload: { user_id: "..." }
})

call_ended

Notifie que l'appel a été terminé.

javascript
channel.on("call_ended", (payload) => {
  // payload: { reason: "completed" }
})

monitor_start / whisper_start / barge_start

Événements de supervision (reçus par l'agent lorsqu'un superviseur surveille, chuchote ou intervient).

javascript
channel.on("whisper_start", (payload) => {
  // payload: { supervisor_id: "...", supervisor_name: "Maria" }
  // L'agent peut entendre le superviseur, mais pas le client
})

Canal : queue:

Canal pour surveiller la file d'attente d'un département en temps réel.

Événements Reçus

customer_joined

Un client a rejoint la file d'attente.

javascript
channel.on("customer_joined", (payload) => {
  // payload: {
  //   call_id: "...",
  //   customer_name: "Carlos",
  //   position: 3,
  //   queue_size: 5
  // }
})

customer_left

Un client a quitté la file d'attente (abandon ou prise en charge).

javascript
channel.on("customer_left", (payload) => {
  // payload: { call_id: "...", reason: "assigned" | "timeout" | "cancelled" }
})

call_assigned

Un appel a été attribué à un agent.

javascript
channel.on("call_assigned", (payload) => {
  // payload: { call_id: "...", user_id: "...", user_name: "João" }
})

queue_update

Mise à jour générale de l'état de la file d'attente.

javascript
channel.on("queue_update", (payload) => {
  // payload: { queue_size: 4, avg_wait_time: 25, online_agents: 3 }
})

Canal : presence:

Canal pour suivre la présence en ligne des agents d'un département. Utilise Phoenix Presence pour la synchronisation distribuée.

Événements Reçus

presence_state

État complet de présence lors de l'entrée dans le canal.

javascript
import { Presence } from "phoenix"

const presence = new Presence(channel)

presence.onSync(() => {
  const users = presence.list((id, { metas }) => ({
    id,
    name: metas[0].name,
    status: metas[0].status,  // "online" | "busy" | "away"
    current_call_id: metas[0].current_call_id
  }))
  console.log("Agents en ligne :", users)
})

presence_diff

Différences incrémentales (arrivées/départs).

javascript
presence.onJoin((id, current, newPres) => {
  console.log(`${newPres.metas[0].name} est en ligne`)
})

presence.onLeave((id, current, leftPres) => {
  console.log(`${leftPres.metas[0].name} est hors ligne`)
})

Canal : notification:

Canal pour les notifications personnelles de l'utilisateur.

Événements Reçus

new_notification

Nouvelle notification.

javascript
channel.on("new_notification", (payload) => {
  // payload: {
  //   type: "call_assigned" | "appointment_reminder" | "system",
  //   title: "Nouvel appel attribué",
  //   body: "Carlos Ferreira attend au Support Technique",
  //   data: { call_id: "..." }
  // }
})

appointment_reminder

Rappel de rendez-vous.

javascript
channel.on("appointment_reminder", (payload) => {
  // payload: {
  //   appointment_id: "...",
  //   title: "Consultation de suivi",
  //   scheduled_at: "2026-02-20T10:00:00Z",
  //   customer_name: "Carlos Ferreira",
  //   minutes_until: 15
  // }
})

Gestion des Erreurs

Reconnexion Automatique

Le socket Phoenix effectue une reconnexion automatique avec backoff exponentiel :

javascript
const socket = new Socket(url, {
  params: { token: accessToken },
  reconnectAfterMs: (tries) => [1000, 2000, 5000, 10000][tries - 1] || 10000
})

Token Expiré

Lorsque le token JWT expire, le socket sera déconnecté. Renouvelez le token et reconnectez-vous :

javascript
socket.onClose(() => {
  // Vérifier si le token a expiré
  if (isTokenExpired(accessToken)) {
    refreshToken().then((newToken) => {
      socket.params.token = newToken
      socket.connect()
    })
  }
})

Identifiants TURN

Pour contourner les restrictions NAT dans les appels WebRTC, obtenez des identifiants TURN :

GET /api/v1/turn/credentials

Exemple de Requête

bash
curl -X GET https://voki.avanter.com.br/api/v1/turn/credentials \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "X-Tenant: avanter"

Réponse de Succès (200)

json
{
  "data": {
    "urls": [
      "turn:voki.avanter.com.br:3478?transport=udp",
      "turn:voki.avanter.com.br:3478?transport=tcp"
    ],
    "username": "1708300800:user123",
    "credential": "abcdef1234567890",
    "ttl": 86400
  }
}

Note

Les identifiants TURN sont temporaires (TTL de 24h) et doivent être renouvelés périodiquement. L'endpoint public /api/v1/call/turn-credentials est également disponible pour les clients sans authentification JWT.

Documentação da API Voki v4.0