AIMERICA
Developers

A phone inside your web app

A dialer in your own page registers one of your AIMERICA lines over a secure WebSocket and carries the audio itself. Your people call and answer without leaving your software; the line keeps every rule it has today.

What it is

To the platform, a browser phone is a phone. It signs in to a line the way a desk phone does and the line treats it the same way: it rings together with the person's other devices, the first to answer takes the call, the others stop; hold, transfer, park, the keypad codes, voicemail and the company's hours and menus all apply unchanged. What is different is where the audio goes: straight into the browser, encrypted between the page and the platform, with the Opus codec and the browser's own echo cancellation.

Your web app does not handle any of that. It asks our API for a short-lived session for the person who is signed in, hands it to a standard SIP-over-WebSocket library in the page (the examples use JsSIP), and shows buttons. There is no plug-in, no extension and nothing to install on the computer.

For the person

One screen

Calls arrive in the app they already have open, with the customer record next to them. Click to call from any number on the page.

For the company

The same line

The person's own number, the company's rules, the call records and recordings, the reports — nothing is split between two systems.

For you

Three calls and a library

Allow your web address, mint a session, start the dialer. The rest is the library's events and your buttons.

How a web app plugs in

  1. Allow your web address. A browser phone may only register from the web addresses (origins) a company has listed — https://app.example.com, not a path. The company's owner or admin adds yours in the app under Settings, or your software does it with PUT /v1/browser-phone/origins (scope phone:manage). A page served from anywhere else is refused, whatever credentials it holds.
  2. Get access to the API. Your own company uses an API key; an app that other companies authorize uses OAuth 2.0 and asks for the scope phone:browser. Never put the key or the token in the page — the browser only ever sees the session from step 3.
  3. Mint a session when the person opens your app. From your server, POST /v1/browser-phone/sessions with the person (their AIMERICA user id or email), your origin and how long the session should live (1 to 12 hours). The answer carries the username (the person's line), a one-time password, the WebSocket address, the realm and the ICE servers. Hand that to the page; keep nothing else.
  4. Start the dialer. The page creates the SIP user agent with the session (the block below), registers, and shows "Ready". From then on the person calls with ua.call(number) and answers incoming calls with session.answer(); hold, mute and the keypad are one call each.
  5. End it when the person signs out. Stop the user agent in the page and DELETE /v1/browser-phone/sessions/{id} from your server. A session also dies by itself at its expiry; your page then asks your server for a new one and registers again.
A person who wants a browser phone outside any web app — a lone softphone page — does not need the API at all: in the app under Settings they create a device password for their line, shown once, and use it with the sample dialer or any SIP-over-WebSocket softphone.

The API

Base address https://api.a1merica.ai/v1, JSON in and out, the usual bearer token. Errors come in the one error shape of the API; writes are rate-limited like every other write (rate limits).

RouteScopeWhat it does
POST /browser-phone/sessionsphone:browserMint a session for one person: a username and a one-time password good for 1-12 h, valid only from the given origin.
DELETE /browser-phone/sessions/{id}phone:browserEnd a session early (the person signed out). Answers 204.
GET /browser-phone/icephone:browserThe ICE servers a page should use right now: a STUN entry always, relay entries with their own 12-hour credentials when the relay is on.
GET /browser-phone/originsphone:manageThe company's allowed web addresses.
PUT /browser-phone/originsphone:manageReplace the list. An origin is scheme + host (+ port), lower case, no path.

Mint a session

POST /v1/browser-phone/sessions
{
  "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": 8                       // 1-12, default 8
}

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": "2026-10-01T21:00:00Z",
  "ice_servers": [
    { "urls": "stun:…" },
    { "urls": ["turn:…:3478?transport=udp", "turn:…:3478?transport=tcp"],
      "username": "1759350000:8135550123", "credential": "…" }
  ]
}

Refused with 403 and a sentence when the origin is not on the company's list, with 404 when the person is not in the company, with 422 when the TTL is out of range.

Allowed web addresses

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

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

ICE servers

GET /v1/browser-phone/ice

200 OK
{ "ice_servers": [ { "urls": "stun:…" }, { "urls": ["turn:…"], "username": "…", "credential": "…" } ] }

The session answer already carries the same list; ask for it again when a page lives longer than 12 hours.

Starting the dialer

Any SIP-over-WebSocket library works (RFC 7118). With JsSIP 3.x from a CDN, the whole dialer is this:

// 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
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,
  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.querySelector("audio#phone");              // <audio id="phone" autoplay></audio>
let current = null;

ua.on("registered", () => show("Ready"));
ua.on("registrationFailed", e => show("Not registered: " + e.cause));   // wrong password, origin not allowed, session expired
ua.on("newRTCSession", ({ session, originator }) => {
  current = session;
  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 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}

Two things the page must do for the browser: be served over HTTPS (a microphone is only granted to secure pages), and create the user agent after a click — browsers only let a page play sound once the person has interacted with it.

Twelve answers

QuestionAnswer
Does it ring with the desk phone and the app?Yes. Every device registered to the line rings; the first to answer takes the call and the others stop ringing.
What caller ID does the far side see?The line's own number, exactly as from the person's desk phone or the AIMERICA app.
Several tabs, or two computers?Each registration is a device on the line; all of them ring. Mint one session per browser, not one per company.
What if nobody answers?The line's own rules run: forwarding, a colleague, then voicemail — the same path as today, with nothing configured on your side.
Hold, transfer, park, conference?Hold from the dialer (hold()). Transfer, park and the rest use the same keypad codes a desk phone on the line uses; dial them with ua.call() or sendDTMF() as the code requires.
Keypad tones to a phone menu?Yes. sendDTMF() sends them the standard way and menus on the far side hear them.
Which browsers?Current Chrome, Edge, Firefox and Safari on desktop and on phones. The page must be HTTPS and the person must allow the microphone.
Behind a strict firewall or a VPN?Signalling travels on TCP 443. Audio goes over UDP when it can; when the network blocks that, it is relayed through our relay on port 3478 (UDP or TCP) using the credentials in ice_servers. A network that allows outgoing 443/TCP and 3478/TCP works.
What about sound quality and codecs?Opus between the browser and the platform, wideband when the other side allows it; the platform converts for the phone network.
Is it encrypted?Yes: TLS on the WebSocket, DTLS-SRTP on the audio, between the page and the platform. Inside the platform calls are handled the same way as every other call.
Can I ship a key or a long-lived password in the page?No. Sessions are 1-12 hours and minted on your server; the password is shown once in the answer and only its digest is kept anywhere. A person without a web app uses a device password they create themselves in the app.
Who can register a browser phone?Only a page served from a web address on the company's list, with a session minted for that origin, or a device password of the line's owner. Ten wrong passwords in fifteen minutes lock that line for that address for fifteen minutes.

Limits and security rules

Try it

The sample dialer is the complete minimal page: fields for the WebSocket address, the line, the password and the realm, buttons to register, call, answer, hold and hang up, a keypad and a status line. Sign in to the app, create a device password under Settings → Browser phone, paste it in, and call your own cell phone.

The sample's source is plain HTML and JavaScript, meant to be copied. Questions, or a web address to allow for a company you are building for: write to us.

Français