2CXVoIPDevelopers
2CXVoIP · Developer documentation

2CXVoIP Communications API — CRM integration guide

This API gives you: agent login, a session token for the embedded softphone, a realtime channel for calls/SMS/presence, and SMS sending. Your CRM owns the user's session (who's logged in, which tab, per-contact conversation history) — this API only provides the communications infrastructure (phone line, balance, and call events).

Base URL: https://webphone.2cxvoip.com

Every error response uses this shape: { "error": "message" }

1. Authentication

POST /auth/login

// body
{ "email": "agent@yourcompany.com", "password": "..." }
// 200
{
  "token": "<jwt, valid 7 days>",
  "user": {
    "id": "uuid", "email": "...", "name": "...", "role": "agent|supervisor|admin",
    "status": "active", "team": "Sales", "line": "+19165551234"
  }
}

Store token and send it as Authorization: Bearer <token> on every subsequent request (including the WebSocket auth message, see §3).

Rate limit: 5 attempts per IP every 15 minutes.

GET /auth/me

Header: Authorization: Bearer <token>. Returns the same shape as user above — use it to validate the token when your app loads.

2. Embedded softphone

GET /api/webphone/token

Header: Authorization: Bearer <token>.

// 200
{ "session_token": "...", "line": "+19165551234", "expires_in": 900 }

session_token is valid for ~15 minutes — request a new one before it expires (e.g. every 10 min while the agent is active). If the agent doesn't have a line assigned yet, the API responds 503 { "error": "No phone line assigned yet..." }.

Softphone widget

Don't install any third-party SDK. Load the 2CXVoIP widget straight from the CDN:

<script src="https://cdn.2cxvoip.com/softphone.js"></script>

This exposes window.TwoCXSoftphone:

TwoCXSoftphone.connect(session_token)        // from GET /api/webphone/token above
TwoCXSoftphone.on('ready', () => {...})
TwoCXSoftphone.on('incoming', ({ from }) => {...})   // show your "incoming call" popup
TwoCXSoftphone.on('callStateChange', ({ state }) => {...})
TwoCXSoftphone.on('error', (err) => {...})

TwoCXSoftphone.dial('+19165551234')          // outbound call
TwoCXSoftphone.answer()
TwoCXSoftphone.reject()
TwoCXSoftphone.hangup()
TwoCXSoftphone.disconnect()

Honest note: the widget removes any carrier dependency from your own code and package.json — that's the exposure that matters in practice. It does not guarantee the browser's network traffic is 100% unrecognizable to someone deliberately inspecting the Network tab; that's outside what a client-side wrapper can control.

3. Realtime channel — WS /ws/presence

Connect one socket per logged-in agent. The first message must always be auth:

→ { "type": "auth", "token": "<jwt from /auth/login>" }
← { "type": "auth_ok", "userId": "uuid" }
   // or { "type": "auth_error" } followed by the socket closing

Messages you can send

{ "type": "status", "status": "available" | "busy" | "in_call" | "offline" }
{ "type": "ping" }   // heartbeat, replies with { "type": "pong" }

Events the server pushes

presence_update — full snapshot whenever any agent's status changes (useful for a wallboard):

{ "type": "presence_update", "agents": [
  { "id": "uuid", "name": "...", "email": "...", "role": "agent",
    "team": "Sales", "line": "+1916...", "status": "available" }
] }

call_incoming — a call is ringing on the agent's line. This is just the signal to show your "incoming call" popup with caller info — the softphone (browser widget, authenticated with session_token) is what actually answers/rings:

{ "type": "call_incoming", "from": "+1...", "to": "+1916...", "callControlId": "..." }

sms_inbound — an inbound SMS already saved, to refresh the contact's conversation thread:

{ "type": "sms_inbound", "conversationId": "uuid", "from": "+1...", "to": "+1916...",
  "text": "...", "contactId": "uuid" }

Every event is delivered only to the socket of the agent who owns the line — there's no broadcast between agents.

GET /api/presence

Initial snapshot (same shape as presence_update.agents) for when your app loads and doesn't have the WebSocket connected yet.

4. SMS

POST /api/sms/send

// body
{ "contact_id": "uuid", "body": "message text" }

Sends from the authenticated agent's assigned line. 404 if the contact doesn't exist, 503 if the agent has no line.

GET /api/contacts/:id/thread

Full history (SMS + calls) for a contact:

{
  "contact": { "id": "...", "phone_e164": "...", "name": "...", "email": "..." },
  "messages": [{ "id", "direction", "body", "sent_at", "agent_name" }],
  "calls": [{ "id", "direction", "status", "duration_seconds", "started_at", "ended_at", "agent_name" }]
}

Useful if your CRM doesn't have its own conversation store yet and wants to lean on this endpoint — not required, just a convenience.

5. What is NOT part of this API

6. CORS

Your frontend's domain needs to be on the server's allowlist before you can call this API from the browser. Email your domain(s) and your client number to support@2cxvoip.com to get them added.