AIMERICAAPI · Développeurs

Téléphone navigateur

Un composeur dans votre propre page Web enregistre une de vos lignes AIMERICA par WebSocket sécurisé et transporte l'audio lui-même. Vos gens appellent et répondent sans quitter votre logiciel ; la ligne garde toutes ses règles.

Ce que c'est

Pour la plateforme, un téléphone navigateur est un téléphone. Il s'enregistre sur une ligne comme un téléphone de bureau, et la ligne le traite pareil : il sonne en même temps que les autres appareils de la personne, le premier qui répond prend l'appel et les autres s'arrêtent ; le renvoi, la messagerie vocale, l'enregistrement, les heures, les menus et les codes du clavier sont ceux de la ligne. Votre application ne gère rien de cela : elle demande à l'API une session pour la personne connectée, la donne à une bibliothèque SIP sur WebSocket standard dans la page, et affiche des boutons. Rien à installer sur l'ordinateur.

Comment fonctionne le WSS

Un navigateur ne peut pas ouvrir une connexion SIP ordinaire ; le composeur parle donc SIP à l'intérieur d'un WebSocket : wss://a1merica.ai/sip-ws, port 443, sous TLS comme n'importe quelle page. De notre côté, ce WebSocket aboutit au même registraire que les téléphones de bureau (authentification digest, le nom d'utilisateur est le numéro à dix chiffres de la personne, le domaine a1merica.ai).

L'audio ne passe jamais par votre serveur. La page capte le microphone avec WebRTC et l'envoie chiffré (DTLS-SRTP, Opus) à notre passerelle média, qui le convertit pour le réseau téléphonique et le remet à l'appel comme n'importe quelle autre branche. Quand un réseau bloque l'audio direct, le navigateur se rabat sur notre relais turn2.a1merica.ai:3478 (UDP ou TCP) avec les identifiants que l'API vous donne ; un réseau qui laisse sortir 443/TCP et 3478/TCP fonctionne.

Comment une application Web s'y branche

  1. Autorisez votre adresse Web. Un téléphone navigateur ne peut s'enregistrer que depuis les origines qu'une entreprise a listées (https://app.exemple.com, sans chemin). Le propriétaire ou un administrateur l'ajoute dans l'application sous Développeur → Téléphone navigateur, ou votre logiciel le fait avec PUT /v1/browser-phone/origins (permission phone:manage). Une page servie d'ailleurs est refusée, quels que soient ses identifiants.
  2. Obtenez l'accès à l'API. Une clé API pour votre propre entreprise, ou une app OAuth 2.0 que d'autres entreprises autorisent, avec la permission phone:browser. La clé ou le jeton reste sur votre serveur ; le navigateur ne voit que la session de l'étape 3.
  3. Émettez une session quand la personne ouvre votre application. Depuis votre serveur, POST /v1/browser-phone/sessions avec la personne (son identifiant ou son courriel), votre origine et, si vous le voulez, une durée. La réponse porte le nom d'utilisateur (sa ligne), un mot de passe à usage unique, le domaine, l'adresse WebSocket et les serveurs ICE. Sans ttl_hours, la session dure jusqu'à ce que vous la supprimiez : personne ne met de mot de passe à jour, et nous ne révoquons jamais ce que vous avez configuré.
  4. Démarrez le composeur. La page crée l'agent utilisateur SIP avec la session (le bloc ci-dessous), s'enregistre et affiche « Prêt ». Ensuite la personne appelle avec ua.call(numéro) et répond avec session.answer() ; garde, sourdine et clavier sont un appel chacun.
  5. Terminez-la quand la personne se déconnecte. Arrêtez l'agent utilisateur dans la page et DELETE /v1/browser-phone/sessions/{id} depuis votre serveur.

Une personne qui veut un téléphone navigateur sans application Web (une page softphone seule) n'a pas besoin de l'API : dans l'application, sous Développeur → Téléphone navigateur, elle crée un mot de passe d'appareil pour sa ligne, affiché une fois, et l'utilise avec le composeur d'exemple ou tout softphone SIP sur WebSocket.

Les routes

RoutePermissionRôle
POST /browser-phone/sessionsphone:browserÉmet une session pour une personne : un nom d'utilisateur et un mot de passe à usage unique valables jusqu'à révocation (ou pour la durée que vous fixez). La session d'une autre personne demande un propriétaire ou un administrateur derrière l'identifiant ; la ligne du propriétaire, le propriétaire
DELETE /browser-phone/sessions/{id}phone:browserTermine une session (la personne s'est déconnectée). Répond 204.
GET /browser-phone/icephone:browserLes serveurs ICE à utiliser maintenant : une entrée STUN toujours, des entrées relais avec leurs identifiants valables 12 heures
GET /browser-phone/originsphone:manageLes adresses Web autorisées de l'entreprise (propriétaire ou administrateur, ou l'application de l'entreprise)
PUT /browser-phone/originsphone:manageRemplace la liste (propriétaire ou administrateur, ou l'application de l'entreprise). Une origine est schéma + hôte (+ port), en minuscules, sans chemin

Émettre une session

POST /v1/browser-phone/sessions
Authorization: Bearer aim_live_…        (your key, or an app token with phone:browser)
{
  "user": "ann@example.com",            // the person's AIMERICA user id or email; they must have a line
  "origin": "https://app.example.com",  // must be on the company's list
  "ttl_hours": 720                      // optional; leave it out and the session lasts until you revoke it
}
201 Created
{
  "id": "bps_8f1c…",
  "username": "8135550123",             // the person's ten-digit line
  "password": "k7Qp…",                  // shown ONCE; only its digest is kept
  "realm": "a1merica.ai",
  "wss_url": "wss://a1merica.ai/sip-ws",
  "expires_at": null,                   // null = until you revoke it
  "ice_servers": [
    { "urls": "stun:turn2.a1merica.ai:3478" },
    { "urls": ["turn:turn2.a1merica.ai:3478?transport=udp", "turn:turn2.a1merica.ai:3478?transport=tcp"],
      "username": "1759350000:8135550123", "credential": "…" }
  ]
}

Refus : 403 origin_not_allowed (l'origine n'est pas sur la liste), 404 user_not_found, 409 no_line (la personne n'a pas de numéro), et l'enveloppe d'erreur habituelle.

Les adresses Web autorisées

PUT /v1/browser-phone/origins           (scope phone:manage)
{ "origins": ["https://app.example.com", "https://staging.example.com:8443"] }

200 OK
{ "origins": ["https://app.example.com", "https://staging.example.com:8443"] }

Démarrer le composeur

// 1. your server minted a session for this person (POST /v1/browser-phone/sessions) and handed it to the page
const s = await fetch("/my-app/phone-session").then(r => r.json());

// 2. the user agent (JsSIP, https://jssip.net - any SIP-over-WebSocket library works the same way)
const socket = new JsSIP.WebSocketInterface(s.wss_url);            // wss://a1merica.ai/sip-ws
const ua = new JsSIP.UA({
  sockets: [socket],
  uri: `sip:${s.username}@${s.realm}`,                               // the person's line @ a1merica.ai
  authorization_user: s.username,
  password: s.password,
  realm: s.realm,
  display_name: "Ann",
  register: true,
  register_expires: 300,                                            // 60-3600 s
  session_timers: false,
  user_agent: "MyApp/1.0 (AIMERICA browser phone)"
});
const media = { mediaConstraints: { audio: true, video: false }, pcConfig: { iceServers: s.ice_servers } };
const speaker = document.getElementById("phone");                   // an audio element with autoplay
let current = null;

ua.on("registered", () => show("Ready"));
ua.on("registrationFailed", e => show("Not registered: " + e.cause));   // wrong password, origin not allowed, session revoked
ua.on("newRTCSession", ({ session, originator }) => {
  current = session;
  // attach the far side's audio HERE (for an outgoing call this event comes after "peerconnection")
  if (session.connection) session.connection.addEventListener("track", e => { speaker.srcObject = e.streams[0]; });
  session.on("peerconnection", ({ peerconnection }) =>
    peerconnection.addEventListener("track", e => { speaker.srcObject = e.streams[0]; }));
  session.on("confirmed", () => show("On a call"));
  session.on("ended",     () => show("Call ended"));
  session.on("failed",    e  => show("Call failed: " + e.cause));
  if (originator === "remote") show("Call from " + session.remote_identity.uri.user);   // offer Answer: current.answer(media)
});
ua.start();

// 3. during the day
ua.call("5551234567", media);                 // a number, a colleague's extension, or a keypad code the line knows
current.answer(media);                         // an incoming call
current.hold();   current.unhold();            // hold / resume
current.mute({ audio: true });  current.unmute({ audio: true });
current.sendDTMF("5");                         // keypad tones (RFC 2833) for a menu on the far side
current.terminate();                           // hang up

// 4. when the person signs out of your app
ua.stop();                                     // then your server: DELETE /v1/browser-phone/sessions/{id}

Appels entrants

Un appel entrant sur la ligne arrive au composeur comme un INVITE SIP sur le même WebSocket que l'enregistrement, en même temps que sur les autres appareils de la personne ; aucune requête à faire, aucun webhook à attendre. Dans JsSIP c'est l'événement newRTCSession avec originator === "remote" : session.answer() prend l'appel (les autres appareils s'arrêtent de sonner), session.terminate({ status_code: 486 }) le refuse (les autres continuent, puis les règles de la ligne : renvoi, messagerie vocale). L'identité de l'appelant est dans From et P-Asserted-Identity (session.remote_identity.uri.user, en E.164). L'en-tête X-AIMERICA-Call-Id de l'INVITE (session.request.getHeader("X-AIMERICA-Call-Id")) porte l'identifiant de l'appel sur la plateforme : le même id que les webhooks calls.session.ringing, calls.session.answered, calls.session.ended et calls.log, que GET /v1/calls/{id}, que l'enregistrement (recordings.ready) et que le raccroché. Si personne ne répond dans le délai de la ligne, l'INVITE est annulé (CANCEL, cause Canceled dans JsSIP) et l'appel suit la messagerie vocale ; un navigateur dont l'enregistrement a expiré ou dont le WebSocket est fermé ne sonne pas.

Douze réponses

QuestionRéponse
Ça sonne avec le téléphone de bureau et l'application ?Oui. Chaque appareil enregistré sur la ligne sonne ; le premier qui répond prend l'appel, les autres s'arrêtent.
Quel identifiant de l'appelant voit l'autre côté ?Le numéro de la ligne, exactement comme depuis le téléphone de bureau ou l'application AIMERICA.
Plusieurs onglets, ou deux ordinateurs ?Chaque enregistrement est un appareil sur la ligne ; tous sonnent. Émettez une session par navigateur, pas une par entreprise.
Si personne ne répond ?Les règles de la ligne s'appliquent : renvoi, un collègue, puis la messagerie vocale — rien à configurer de votre côté.
Garde, transfert, parcage, conférence ?La garde depuis le composeur (hold()). Transfert, parcage et le reste utilisent les mêmes codes du clavier qu'un téléphone de bureau sur la ligne ; composez-les avec ua.call() ou sendDTMF().
Tonalités du clavier vers un menu téléphonique ?Oui. sendDTMF() les envoie de façon standard (RFC 2833) et les menus de l'autre côté les entendent.
Quels navigateurs ?Chrome, Edge, Firefox et Safari actuels, sur ordinateur et sur téléphone. La page doit être en HTTPS et la personne doit autoriser le microphone.
Derrière un pare-feu strict ou un VPN ?La signalisation voyage sur TCP 443. L'audio passe en UDP quand il le peut ; sinon il est relayé par notre relais sur le port 3478 (UDP ou TCP) avec les identifiants de ice_servers.
Qualité du son et codecs ?Opus entre le navigateur et la plateforme, large bande quand l'autre côté le permet ; la plateforme convertit pour le réseau téléphonique.
Est-ce chiffré ?Oui : TLS sur le WebSocket, DTLS-SRTP sur l'audio, entre la page et la plateforme.
Puis-je mettre une clé ou un mot de passe durable dans la page ?Non. Les sessions sont émises sur votre serveur ; le mot de passe est affiché une fois dans la réponse et seul son condensé est conservé. Une personne sans application Web utilise un mot de passe d'appareil qu'elle crée elle-même.
Qui peut enregistrer un téléphone navigateur ?Seulement une page servie depuis une adresse Web de la liste de l'entreprise, avec une session émise pour cette origine, ou un mot de passe d'appareil du titulaire de la ligne. Dix mauvais mots de passe en quinze minutes verrouillent la ligne pour cette adresse pendant quinze minutes.

Limites et règles de sécurité

  • Une session dure jusqu'à ce que vous la révoquiez, ou pour la durée que vous fixez (1 heure à un an), et elle est liée à l'origine pour laquelle elle a été émise.
  • Les origines sont exactes : schéma, hôte et port, sans chemin ni joker. Ajoutez une adresse de test comme entrée à part.
  • Le mot de passe d'une session ou d'un appareil est affiché une fois ; la plateforme n'en garde qu'un condensé et ne l'écrit jamais dans un journal.
  • Une session ou un mot de passe d'appareil enregistre un navigateur. Pour deux ordinateurs, émettez deux sessions.
  • Un téléphone navigateur peut appeler ce que le téléphone de bureau de la personne peut appeler : les numéros d'urgence toujours ; les destinations surtaxées et la plupart des internationales sont bloquées sauf si l'entreprise les a ouvertes ; les plafonds de l'entreprise s'appliquent.
  • Les appels d'urgence (911) partent avec l'adresse d'urgence enregistrée de la ligne, comme depuis un téléphone de bureau. Un navigateur n'est pas un bon endroit pour un appel d'urgence quand un téléphone est à portée de main ; dites-le à vos gens.
  • Les sessions et les changements d'origines comptent comme des écritures pour les limites de débit. Émettez une session quand la personne ouvre l'application, pas à chaque page.
  • Révocation : supprimez une session, ou retirez l'origine de la liste ; le prochain enregistrement échoue et l'enregistrement courant expire dans son intervalle (cinq minutes dans l'exemple). Nous ne révoquons jamais de notre côté.
  • Les identifiants du relais dans ice_servers expirent après 12 heures : redemandez-les (GET /v1/browser-phone/ice) pour une page qui vit plus longtemps.
Le guide complet avec le composeur d'exemple à copier : aimerica.si/browser-phone (en anglais). Essayez-le à la main d'abord : un mot de passe d'appareil, le composeur d'exemple, un appel.