All developer pages

Developers

The phone in your CRM

Show your team's phone next to the record they are working on. They dial from any number in the CRM, take incoming calls, mute, hold and transfer, and write call notes, without leaving the page. Your CRM hears every call ring, connect and end.

Before you start

The phone runs in an iframe served by your console. It may be framed only by the sites your account's administrator lists, and by none until one is listed.

  • In the console, open Developer, then Phone in your CRM, and add your CRM's address, such as https://crm.example.com. One line per site. https://*.example.com allows every subdomain of example.com. Addresses must use https; http://localhost is allowed for development.
  • Copy the account ID shown on the same card. The SDK needs it.
  • Each user connects once per browser: the phone shows a Connect button, which opens a small sign-in window on your console. If they are already signed in to the console, they only confirm. Their session in the CRM ends when they sign out of the phone, or when the administrator removes the CRM's address from the list.
  • The phone needs the microphone. The SDK asks for it on the iframe (allow="microphone; autoplay"); if you build the iframe yourself, add the same attribute.
  • If your CRM sends Cross-Origin-Opener-Policy: same-origin, use same-origin-allow-popups instead, so the sign-in window can hand the session back to the phone.

Quick start

Load the SDK from your console's address (replace app.example.com with yours, such as https://cc.ringura.com), give it a container and your account ID, and call dial() with any number. A data-call attribute on your CRM's phone links is all the click-to-call wiring you need:

<!-- 1. Load the SDK from your console's address (app.example.com below). -->
<script src="https://app.example.com/embed/phone.js"></script>

<!-- 2. A place for the phone, such as a side panel. -->
<aside id="phone" style="width: 380px; height: 640px"></aside>

<script>
  // 3. Your account ID is on the console's Developer page, under "Phone in your CRM".
  const phone = new EmbeddedPhone({
    container: '#phone',
    account: '00000000-0000-4000-8000-000000000000',
    host: 'https://app.example.com',
  });

  // 4. Click-to-call from any number in your CRM.
  document.addEventListener('click', async (event) => {
    const link = event.target.closest('[data-call]');
    if (!link) return;
    event.preventDefault();
    try {
      const result = await phone.dial(link.dataset.call, {
        contact: { id: link.dataset.contactId, name: link.dataset.contactName },
      });
      console.log('dialling via', result.via); // 'phone' or 'click_to_call'
    } catch (error) {
      console.warn('not dialled:', error.code, error.message);
    }
  });

  // 5. Log each finished call against the record it was for.
  phone.on('call.ended', (call) => {
    saveActivity({
      contactId: call.contact?.id,
      direction: call.direction,
      seconds: call.durationSeconds,
      ringuraCallId: call.interactionId,
    });
  });
</script>

Commands

Each command returns a promise. A refusal rejects with an error whose code says why, such as signed_out, bad_number, busy, no_call, or the outbound gate's reason for a call it refused (calling hours, the do-not-call list, consent).

  • dial(number, { contact }): places the call. When the phone is connected, the browser dials and the result is { via: 'phone' }. When it is not (no microphone, or the user is on a desk phone or the mobile app), the platform rings the user's own phone first and then the number, and the result is { via: 'click_to_call', requestId, status }. Either way the call goes through the same checks as every outbound call.
  • hangUp(): ends the call that is up.
  • answer(): answers the call that is ringing.
  • setContact(contact): the CRM record the user is on, shown in the phone and carried in every call event until it changes. Fields: id, name, number, company, url (https only) and, if your CRM keeps it, ringuraContactId. null clears it.
  • getState(): whether the user is connected, the phone's status, the call that is up and the current contact.

Events

Listen with phone.on(name, handler), or phone.on('*', (name, data) => ...) for all of them. Call events carry callId (the call's SIP Call-ID, available at once), interactionId (the platform's id for the call, once known: use it with the API and webhooks), direction, number and the contact you set.

  • ready: the phone has loaded; commands sent before this wait for it.
  • signedIn and signedOut: the user connected or disconnected the phone.
  • call.ringing: a call is ringing, inbound or outbound.
  • call.answered: the call connected.
  • call.ended: the call ended, with answered and durationSeconds.
  • recording.available and transcript.available: the call's recording or transcript is ready, with its interactionId. Fetch them with the API.
  • note.saved: the user saved a call note in the phone; it is on the call in the console.
  • contact.changed: the contact context changed.

Without the SDK

The SDK is a thin wrapper. You can speak the protocol yourself: frame /embed/phone?account=<account ID>&origin=<your page's origin> and exchange messages shaped { protocol: 'cc.phone', version: 1, type, ... }. Commands are { type: 'command', id, command, args } and are answered with { type: 'response', id, ok, result } or { ok: false, error: { code, message } }. Events are { type: 'event', event, data }.

// Without the SDK: the same API over postMessage. Check both the origin and the window.
const PHONE_ORIGIN = 'https://app.example.com';
const frame = document.querySelector('#phone-frame'); // <iframe src=".../embed/phone?account=...&origin=..." allow="microphone; autoplay">

window.addEventListener('message', (event) => {
  if (event.origin !== PHONE_ORIGIN || event.source !== frame.contentWindow) return; // not the phone
  const msg = event.data;
  if (msg?.protocol !== 'cc.phone') return;
  if (msg.type === 'event') console.log(msg.event, msg.data);     // e.g. call.ringing
  if (msg.type === 'response') console.log(msg.id, msg.ok ? msg.result : msg.error);
});

// Send commands to the phone's origin only, never '*'. Wait for the 'ready' event first.
frame.contentWindow.postMessage({
  protocol: 'cc.phone', version: 1, type: 'command', id: 'call-1',
  command: 'dial', args: { number: '+442079460958', contact: { id: 'crm-42', name: 'Ada Lovelace' } },
}, PHONE_ORIGIN);

Security

  • Framing: the phone page is served with Content-Security-Policy: frame-ancestors naming only your site, when it is on the list, and frame-ancestors 'none' otherwise. No other site can show it, and a page that frames your CRM cannot either.
  • Messages: the phone accepts commands only from the window that frames it, at the origin it was served for, and sends its answers and events to that origin only. Check event.origin and event.source on your side too; the SDK does.
  • Sign-in: the phone never sees the user's password. The sign-in window runs on your console, and the session it opens for the phone works only for the phone's own actions and only from the listed site. Removing the site from the list ends those sessions at once.
  • Your administrator can switch the phone in the CRM off for the account, a team or a person on the Feature access page. Calling itself stays available in the console.

Click-to-call from your server

If you would rather start calls from your back end, use POST /api/v1/calls with an API token that has the calls:write scope (see the Calls reference). The named agent's phone rings first, then the number, presented from one of your verified numbers. A refusal answers 409 with the reason. Conversation and message events reach your server as webhooks.

# Server-side click-to-call with an API token that has the calls:write scope.
# The agent's own phone rings first; when they answer, the number is dialled.
curl https://api.example.com/api/v1/calls \
  -H "Authorization: Bearer $RINGURA_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: crm-activity-8812" \
  -d '{ "number": "+44 20 7946 0958", "agentId": "<agent id>", "purpose": "service" }'

Building an integration for your customers?

CRM vendors can list the phone as a ready-made integration. Talk to us about partner access and a test account.