Dora Cell SDK
The Dora Cell SDK lets you add VoIP calling to JavaScript and React applications. It authenticates with Dora Cell API tokens, provisions SIP credentials, registers a browser SIP user, and exposes a simple event-driven API for calls, caller IDs, and wallet balance.
Browser Support
For the best experience, we recommend using Chromium-based browsers (Chrome, Microsoft Edge, Brave, Opera). Our WebRTC implementation is highly optimized for the Chromium engine.
High Performance
Engineered for reliable, low-latency voice communication with crystal-clear audio quality.
Secure Auth & Sessions
Login credentials authentication with automatic SIP provisioning, cookie persistence, and session management.
Core SDK Installation
Install the base SDK to use it in browser-based JavaScript apps, including vanilla JS, React, Vue, Angular, and other frameworks.
1npm install @dora-cell/sdkInitialization & Authentication
Initialize the SDK using User Login Credentials (recommended for dashboards and agents, with automatic browser cookie session restoration).
1import { DoraCell } from "@dora-cell/sdk";2 3// ─── Option 1A: Agent Account Login (uses EMAIL) ───4const sdk = new DoraCell({5 auth: {6 type: "login",7 userType: "agent",8 email: "agent@example.com",9 password: "password",10 },11 environment: "production",12 debug: false13});14 15// ─── Option 1B: Admin Account Login (uses USERNAME) ───16// const sdk = new DoraCell({17// auth: {18// type: "login",19// userType: "admin",20// username: "admin_user",21// password: "password",22// },23// environment: "production",24// debug: false25// });26 27sdk.on("connection:status", ({ status, extension, error }) => {28 console.log("Connection:", status, extension, error);29});30 31await sdk.initialize();32console.log("SDK is ready!");33 34// To sign out and clear browser cookies:35// await sdk.logout();Making & Receiving Calls
The SDK provides a simple interface for outbound calls, incoming calls, mute controls, and remote audio streams.
1// Place an outbound call2const call = await sdk.call("+2348000000000", {3 metadata: { customerId: "cus_123" }4});5 6call.mute();7call.unmute();8call.hangup();9 10// Answer an incoming call11sdk.on("call:incoming", async (call) => {12 console.log("Incoming call from:", call.remoteNumber);13 await sdk.answerCall();14});15 16sdk.on("call:stream", (call, stream) => {17 const audio = document.querySelector("audio#remote-audio");18 if (audio) audio.srcObject = stream;19});Event Handlers
Stay updated with the call state by subscribing to SDK events.
1// Listen for connection status2sdk.on("connection:status", ({ status, error }) => {3 console.log("SIP Status changed:", status);4});5 6// Listen for call transitions7sdk.on("call:connected", (call) => {8 console.log("Conversation started with:", call.remoteNumber);9});10 11sdk.on("call:ended", (call, reason) => {12 console.log("Call ended. Reason:", reason);13});14 15sdk.on("call:failed", (call, error) => {16 console.error("Call failed:", error);17});React SDK Installation
The React SDK requires the Core SDK as a peer dependency. Import the bundled stylesheet once in your root layout or app entry.
1npm install @dora-cell/sdk @dora-cell/sdk-react2# or3pnpm add @dora-cell/sdk @dora-cell/sdk-reactSetting up the Provider
Wrap your application tree with the DoraCellProvider to enable global call management using login credentials.
1import { DoraCellProvider } from "@dora-cell/sdk-react";2import "@dora-cell/sdk-react/styles.css";3 4export default function RootLayout({ children }) {5 return (6 <DoraCellProvider7 config={{8 auth: {9 type: "login",10 userType: "agent", // For Admin accounts, use userType: "admin" and replace 'email' with 'username'11 email: "agent@example.com",12 password: "password",13 },14 environment: "production"15 }}16 autoInitialize={true}17 >18 {children}19 </DoraCellProvider>20 );21}Hooks Overview
useCall()
Manage active calls, mute, and duration.
useConnectionStatus()
Monitor SIP registration, errors, and invoke logout().
useWallet()
Read and refresh wallet balance.
useExtensions()
Fetch caller IDs and switch extensions.
1import { useCall, useConnectionStatus } from "@dora-cell/sdk-react";2 3export function Dialer() {4 const { call, callStatus, callDuration } = useCall();5 const { isConnected, connectionStatus, logout } = useConnectionStatus();6 7 return (8 <div>9 <div className="flex justify-between items-center mb-4">10 <p>Status: {connectionStatus}</p>11 {isConnected && (12 <button onClick={() => logout()} className="text-red-500 text-sm">13 Sign out14 </button>15 )}16 </div>17 {callStatus === "ongoing" && <p>{callDuration}</p>}18 <button 19 onClick={() => call("+2348000000000")}20 disabled={!isConnected || callStatus !== "idle"}21 >22 {callStatus === "idle" ? "Call" : "Calling..."}23 </button>24 </div>25 );26}Built-in UI Components
We provide production-ready components that fit perfectly into your application. Below is a complete overview of the core components and their props.
Dialpad
A flexible keypad for entering numbers and switching between Caller IDs.
initialNumber?: stringPre-fill the dialer input.showKeys?: booleanShow numeric keypad by default (true).className?: stringCustom CSS classes.availableExtensions?: ArrayOverride auto-fetched caller IDs.selectedExtension?: stringManually select the active caller ID.onExtensionChange?: fnCallback when caller ID changes.onCallInitiated?: fnCallback after a call starts.metadata?: RecordMetadata passed through to sdk.call().CallInterface
A slide-over interface that handles the audio and call lifecycle automatically.
isOpen?: booleanControls visibility.onOpenChange?: fnCallback to request open/close.onCallEnded?: fnCallback when a call ends.maximizeIcon?: NodeCustom maximize icon.minimizeIcon?: NodeCustom minimize icon.ringtoneUrl?: stringReserved for custom ringtone audio.ringbackUrl?: stringReserved for custom ringback audio.1import { useState } from "react";2import { CallInterface, Dialpad, CreditBalance } from "@dora-cell/sdk-react";3 4function App() {5 const [dialerOpen, setDialerOpen] = useState(false);6 7 return (8 <>9 <header>10 <CreditBalance />11 </header>12 13 {/* Handles all incoming and outgoing call UI */}14 <CallInterface 15 isOpen={dialerOpen} 16 onOpenChange={setDialerOpen} 17 onCallEnded={() => console.log('Call Ended')}18 />19 20 <main>21 <Dialpad 22 initialNumber="+2348000000000"23 showKeys={true}24 metadata={{ source: "docs" }}25 onCallInitiated={(num) => console.log('Calling', num)}26 />27 </main>28 </>29 );30}Component Previews
Dialpad Component
A full-featured dialer with Caller ID selection and interactive keys.
Call Interface Component
Responsive floating interface for active and incoming call management.
John Doe
+234 701 555 0123 • Incoming...
John Doe
+234 701 555 0123 • On Call
GoHighLevel CDN Installation
Integrate Dora Cell directly across your GoHighLevel (GHL) agency without build tools or bundlers. Use our pre-compiled CDN assets inside your Agency's Custom JS & CSS fields.
💡 Where to find this: Navigate to your GoHighLevel Agency Settings at https://app.gohighlevel.com/settings/company?tab=whitelabel
1. Custom CSS Field (Styles)
1@import url("https://unpkg.com/@dora-cell/ghl@latest/dist/core.css");2. Custom JS Field (Production Script)
1<!-- Production Bundle (automatically connects to Dora Cell Production API) -->2<script src="https://unpkg.com/@dora-cell/ghl@latest/dist/core.js"></script>Configuration & Setup
The GoHighLevel widget automatically injects a floating dialer button and overlay into the GoHighLevel CRM. We provide two distinct scripts based on your deployment environment:
Production Script
Defaultdist/core.js
Hardcoded for the Dora Cell production environment. There is no environment selector dropdown shown to the user. Agents simply sign in and start dialing immediately.
Staging / Sandbox Script
Testingdist/core.staging.js
Includes an environment selector dropdown inside the widget settings so developers and QA teams can test against staging or development servers during setup.
1<!-- For Staging / Sandbox testing, replace core.js with core.staging.js -->2<script src="https://unpkg.com/@dora-cell/ghl@latest/dist/core.staging.js"></script>Embedded Dialer UI & Features
Once installed, the widget mounts seamlessly inside GoHighLevel:
1. Login Credentials Authentication
Agents sign in using their registered Email Address and Password (or Admins using their Username and Password). Once authenticated, the widget automatically requests SIP line provisioning and restores the session across page navigation.
2. Click-to-Dial & In-App Keypad
Agents can dial numbers directly from the numeric keypad or select from available Caller IDs (Extensions). Active calls feature real-time call duration timers, mute/unmute toggles, and instant hangup.
3. Live Credit Balance & Connection Status
The widget continuously displays the account's real-time credit balance (e.g., ₦1,250.00) and connection health status indicator, ensuring agents know their line status before initiating outbound calls.
Partner API
The Partner API lets you build and run your own telecom business on top of Dora Cell — the reseller model. You provision and manage your own end-customers, give them phone numbers, embed calling into your product with the Dora Cell SDK, and read their usage to bill them however you like. Dora Cell never bills your customers; it meters your aggregate usage and bills you wholesale.
How the model works
You hold a prepaid wholesale wallet (your headroom with Dora Cell). As your customers place calls, Dora Cell debits that wallet at your wholesale rate. You charge your own customers whatever you want (your retail pricing) and collect from them off-platform. When your wholesale headroom runs out, all of your customers stop at their next call — so keep it funded.
Base URL
1# Production2https://api.cell.usedora.com/api3 4# Development / sandbox5https://dev.api.cell.usedora.com/apiEvery partner endpoint lives under /partner and requires a partner access token (see Authentication). Your partner account and its first operator login are created for you by Dora Cell — reach out to your Dora Cell contact to get provisioned and to have your wholesale wallet funded.
The typical integration flow
- Sign in as a partner operator to get an access token.
- Create an end-customer (account + business profile + wallet, provisioned automatically).
- Assign a phone number to the customer from inventory.
- Fetch the customer's SIP credentials and embed the Dora Cell SDK to register their softphone.
- Your customer places and receives calls.
- Read usage to bill your customers; top up your wholesale wallet as needed.
Authentication
Authenticate with your partner operator email and password to receive a bearer token. Send that token in the Authorization header on every subsequent request.
/partner/login1curl -X POST https://api.cell.usedora.com/api/partner/login \2 -H "Content-Type: application/json" \3 -d '{ "email": "ops@yourcompany.com", "password": "••••••••" }'Response:
1{2 "status": "success",3 "operator": { "id": "…", "name": "Jane Ops", "email": "ops@yourcompany.com", "role": "owner" },4 "partner": { "id": "…", "name": "Acme Telco", "slug": "acme-telco", "settlement_currency": "NGN" },5 "token": "42|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"6}Use the token on every partner request:
1const BASE = "https://api.cell.usedora.com/api";2 3async function partner(path, { method = "GET", token, body } = {}) {4 const res = await fetch(BASE + path, {5 method,6 headers: {7 "Content-Type": "application/json",8 Accept: "application/json",9 ...(token ? { Authorization: "Bearer " + token } : {}),10 },11 body: body ? JSON.stringify(body) : undefined,12 });13 if (!res.ok) throw new Error((await res.json()).message || res.statusText);14 return res.json();15}16 17// Sign in18const { token } = await partner("/partner/login", {19 method: "POST",20 body: { email: "ops@yourcompany.com", password: process.env.PARTNER_PASSWORD },21});404.Customers
A customer is one of your end-customer accounts. Creating one provisions a business profile, a wallet, and the SIP / billing identity automatically — everything needed to place calls.
Create a customer
/partner/customers1const { data: customer } = await partner("/partner/customers", {2 method: "POST",3 token,4 body: {5 business_name: "Nice Venture",6 full_name: "Chima Okeke",7 username: "nice", // unique8 email: "chima@nice.co", // unique9 password: "temp-password-123", // min 8 chars10 country_code: "234", // dial code; optional (defaults to +234)11 currency: "NGN", // optional; defaults to your settlement currency12 },13});14// customer.id, customer.primary_profile, customer.walletList customers
/partner/customers?per_page=251{2 "status": "success",3 "data": {4 "current_page": 1,5 "total": 2,6 "data": [7 { "id": "…", "username": "nice", "full_name": "Chima Okeke", "email": "chima@nice.co",8 "primary_profile": { "business_name": "Nice Venture", "status": "active" },9 "wallet": { "currency": "NGN", "balance": "0.00" } }10 ]11 }12}Suspend or reactivate a customer
Suspending stops the customer from placing calls at their next credit sync — a reversible flag, no data is lost. Reactivating restores them automatically.
/partner/customers/{id}1await partner("/partner/customers/" + customer.id, {2 method: "PATCH", token,3 body: { status: "suspended" }, // or "active"4});Get a customer's SIP credentials
These credentials register the customer's softphone. Hand them to the Dora Cell SDK (see the Core SDK section) so your product can place calls on the customer's behalf.
/partner/customers/{id}/sip1{2 "status": "success",3 "data": {4 "sip_uri": "sip:agent_38d15a0f80a7fe@dcell-blive.usedora.com",5 "username": "agent_38d15a0f80a7fe",6 "password": "…",7 "domain": "dcell-blive.usedora.com",8 "ws_url": "wss://dcell-blive.usedora.com:8089/ws"9 }10}Numbers (DIDs)
Assign phone numbers from Dora Cell's shared inventory to your customers. Numbers are priced against your wholesale rate card and are country-scoped.
List available numbers for a country
/partner/dids/available?country_code=2341{2 "status": "success",3 "data": [4 { "extension": "2348012345678", "country": "Nigeria", "country_code": "234",5 "wholesale_monthly_cost": "2000.00", "wholesale_currency": "NGN" }6 ]7}Assign a number to a customer
/partner/customers/{id}/dids1const { data: number } = await partner("/partner/customers/" + customer.id + "/dids", {2 method: "POST", token,3 body: { extension: "2348012345678" },4});5// number.extension, number.status, number.wholesale_costAssignment is wholesale-gated:
402— insufficient wholesale headroom to cover one month of rental.422— your wholesale rate card doesn't price that country yet.409— the number is already assigned.
List a customer's numbers
/partner/customers/{id}/didsUsage
Read per-customer call activity and your own wholesale spend for a period, then bill your customers however you price them. Customers are identified by Dora Cell ids only.
/partner/usage?from=2026-07-01&to=2026-07-311{2 "status": "success",3 "period": { "from": "2026-07-01", "to": "2026-07-31" },4 "customers": [5 { "customer_id": "…", "username": "nice", "full_name": "Chima Okeke", "calls": 42, "minutes": 138.5 }6 ],7 "totals": { "calls": 42, "minutes": 138.5, "wholesale_spend": 2216.0 }8}Webhooks
Webhooks let your app react to events in real time. The key one is call.completed: when one of your customers finishes a call, Doracell rates it at your retail rate and POSTs you the cost — so your app deducts it from that customer's wallet automatically. You never build call rating; you just handle an event and debit a wallet.
Discover the available events
/partner/webhooks/events1{2 "status": "success",3 "data": [4 { "event": "call.completed",5 "description": "A call finished — deduct its cost (at your retail rate) from your customer's wallet." }6 ]7}Register an endpoint
Pass the events you want (or ["*"] for all). The response includes a signing secret — store it; it verifies every delivery.
/partner/webhooks1const { data: hook } = await partner("/partner/webhooks", {2 method: "POST", token,3 body: { url: "https://yourapp.com/dora/webhook", events: ["call.completed"] },4});5// hook.secret → "whsec_…" (store this; it signs deliveries)The call.completed payload
Every delivery is wrapped in an envelope; the event data is under data.
1{2 "id": "…", // delivery id — also the X-Dora-Delivery header; use as an idempotency key3 "event": "call.completed",4 "created_at": "2026-07-26T01:05:27+00:00",5 "data": {6 "customer_id": "…", // which of your customers to charge7 "minutes": 6.0,8 "cost": 120.0, // AT YOUR RETAIL RATE — deduct this from the customer's wallet9 "currency": "NGN",10 "wholesale_cost": 96.0, // what Doracell charged you (for reconciliation)11 "destination": "2348012345678",12 "reference": "mb123456", // the underlying call id13 "occurred_at": "2026-07-26T01:05:20+00:00"14 }15}Verify the signature
Each request carries X-Dora-Timestamp and X-Dora-Signature. The signature is HMAC-SHA256("{timestamp}.{raw body}", secret). Recompute it over the raw request body and compare before trusting the event.
1import crypto from "crypto";2 3// IMPORTANT: verify against the RAW body, so capture it (e.g. express.raw()).4app.post("/dora/webhook", express.raw({ type: "application/json" }), (req, res) => {5 const secret = process.env.DORA_WEBHOOK_SECRET; // whsec_…6 const timestamp = req.header("X-Dora-Timestamp");7 const signature = req.header("X-Dora-Signature");8 const raw = req.body.toString("utf8");9 10 const expected = crypto.createHmac("sha256", secret)11 .update(timestamp + "." + raw)12 .digest("hex");13 14 if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {15 return res.status(400).send("bad signature");16 }17 18 const { id, event, data } = JSON.parse(raw);19 20 // Idempotency: skip if you've already processed this delivery id.21 if (event === "call.completed") {22 // await wallets.debit(data.customer_id, data.cost); // your own wallet23 }24 25 res.sendStatus(200); // respond 2xx or we retry with backoff26});id (or data.reference) to avoid double-deducting.Billing & wholesale wallet
Your wholesale wallet is your prepaid headroom with Dora Cell. Voice calls debit it per-minute at your wholesale rate; numbers debit it monthly. When it hits zero, all of your customers are blocked at their next call until you top up (top-ups are handled by Dora Cell as a settlement action).
/partner/wallet1{2 "status": "success",3 "data": [ { "id": "…", "currency": "NGN", "balance": "48000.00", "status": "active" } ]4}Ledger
/partner/wallet/ledger?per_page=50Retail pricing (what you charge your customers) is your own — set it up as retail rate cards in the Partner Console. Dora Cell does not apply or collect your retail prices; it only meters usage and bills you wholesale, and exposes the usage so you can bill your customers.
End-to-end example
1// 1. Sign in2const { token } = await partner("/partner/login", {3 method: "POST",4 body: { email: "ops@yourcompany.com", password: process.env.PARTNER_PASSWORD },5});6 7// 2. Create the customer8const { data: customer } = await partner("/partner/customers", {9 method: "POST", token,10 body: { business_name: "Nice Venture", full_name: "Chima Okeke",11 username: "nice", email: "chima@nice.co", password: "temp-123456", country_code: "234" },12});13 14// 3. Assign a number15const [firstAvailable] = (await partner("/partner/dids/available?country_code=234", { token })).data;16await partner("/partner/customers/" + customer.id + "/dids", {17 method: "POST", token, body: { extension: firstAvailable.extension },18});19 20// 4. Get SIP credentials to embed in the Dora Cell SDK21const { data: sip } = await partner("/partner/customers/" + customer.id + "/sip", {22 method: "POST", token,23});24 25// 5. Hand `sip` to the Dora Cell SDK in your product to register the softphone.26console.log("Ready to call as", sip.username);