SautiKit
/ai-agents/pricing/docs/api/blog
sign inStart building

Let users call from the browser: WebRTC click-to-call, no phone needed

Let users place and receive phone calls directly from a webpage using Sautikit's SIP token flow and browser SDK.

use-casewebrtcbrowser-callingsipclick-to-call

Next Steps

  • Browser Calling with WebRTCSautikit supports browser calling via short-lived SIP tokens minted server-side. The token carries workspace identity, is verified by the SIP gateway, and attributes call billing to your KES wallet.
  • Browser Calling with WebRTCYour server mints a 5-minute HS256 SIP token via POST /v1/sip/token and hands it to the browser SDK. The SDK connects to the SIP gateway and places or receives calls attributed to your KES wallet.
  • CallsEvery phone call in Sautikit is a call record with a direction (inbound or outbound), a sequence of lifecycle states, and an optional recording. Cost is debited at hangup based on answered duration.
SautiKit

Programmable voice infrastructure for Africa. Buy numbers, place calls, and bill per second, all in local currency, via API.

All systems operational

Product

AI voice agentsBroadcastsNumbersCalls & routingRecordingsWallet & billingPricing

Developers

DocumentationAPI referenceVoice actionsWebhooksErrorsMCP serverQuickstartAI prompt

Compare

vs Africa's Talkingvs Twiliovs Infobipvs Vapi, Retell & BlandMigrate from Africa's TalkingAll comparisons

Company

AboutBlogConsole

© 2026 Sautikit. All rights reserved • Powered by Helloduty

Terms of ServicePrivacy Policy

Sautikit provides voice API services for application developers. Numbers provisioned on this platform are not configured for emergency calling (e.g. 999 / 112). Do not use Sautikit numbers as a replacement for a primary phone line.

Summary

Browser calling lets a user place or receive a phone call through their web browser using WebRTC, without needing a phone handset or a separate VoIP app. Sautikit implements this via a short-lived SIP token that your server mints per session and hands to the browser. The browser SDK uses the token to authenticate against Sautikit's SIP gateway, which bridges the WebRTC audio to the public telephone network. All billing and call records are attributed to your workspace.

Who this is for

  • Support portals where agents handle calls from within a CRM or ticketing web UI.
  • Customer-facing applications where end users should be able to call a business number with one click, without leaving the browser.
  • Sales tools that need click-to-dial from a contact page.
  • Platforms where the user may not have a phone number but needs to reach a phone line (for example, users contacting support from a desktop app).
  • Any application that currently routes inbound calls to physical phones but wants to also support browser-based agents.

How it works

SIP token mint flow
SIP token mint flowBrowser requests SIP token from your server. Your server calls Sautikit API with API key. Token returned to browser. Browser connects to SIP gateway using token. Token refreshed before expiry.BrowserYour serverSautikit APIGET /api/sip-tokenPOST /v1/sip/token (Bearer $API_KEY){ token, expires_at }{ token, expires_at }SIP WebSocket + token (HS256 JWT)refresh every ~4 min1.2.3.
The API key never leaves your server. The browser only receives a short-lived SIP token (5-minute TTL) for SIP gateway authentication.

Security model

The API key never leaves your server. Only the short-lived SIP token (5-minute TTL) is handed to the browser. Even if intercepted, an expired token grants no access. Each browser session should receive its own token; do not share one token across multiple users or tabs.

Your token endpoint should be behind your own authentication layer so that only logged-in users receive tokens. A user who obtains a valid token can place calls that are billed to your workspace, so treat the token endpoint with the same care as any billing-sensitive route.

Incoming calls to the browser

The browser SDK can also receive inbound calls. When a call arrives at a Sautikit number whose routing webhook returns a Dial action targeting a SIP URI associated with the browser session, the SDK fires a client.on("incoming", (call) => ...) event. The user can accept or reject the call from the UI.

API surface

Endpoints you call (server-side):

  • POST /v1/sip/token: mint a short-lived SIP token for the browser session.
  • GET /v1/calls: list calls placed by browser sessions for CDR and billing review.
  • GET /v1/calls/{call_sid}: retrieve individual call detail records.

Browser SDK (client-side):

  • new SautikitClient({ tokenUrl }): initialise with your token endpoint URL.
  • client.connect(): establish the WebSocket connection to the SIP gateway.
  • client.call(destination, { from }): place an outbound call.
  • client.on("incoming", handler): receive inbound calls.
  • call.hangup(): end the call.
  • call.on("answered" | "ended" | "ringing"): call state events.

Related concept:

  • Browser calling with WebRTC concept: token flow internals, signing key rotation, and security considerations.

Example

Server-side token endpoint (Node.js / Express)

import express from "express";
import { requireAuth } from "./auth";   // your own auth middleware
 
const app = express();
 
// Mint a SIP token for an authenticated user's browser session
app.post("/api/sip-token", requireAuth, async (req, res) => {
  const response = await fetch("https://api.sautikit.com/v1/sip/token", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SAUTIKIT_API_KEY}`,
    },
  });
 
  if (!response.ok) {
    return res.status(502).json({ error: "Failed to mint SIP token" });
  }
 
  const { token, expires_at } = await response.json();
  // Return token to the authenticated browser session only
  res.json({ token, expires_at });
});
 
app.listen(3000);

Client-side browser code

import { SautikitClient } from "@sautikit/browser";
 
const client = new SautikitClient({
  // Your server endpoint; must require user authentication
  tokenUrl: "/api/sip-token",
});
 
// Connect to the SIP gateway on page load
await client.connect();
console.log("WebRTC ready");
 
// ── Outbound call ────────────────────────────────────────────────
async function startCall(destinationNumber) {
  const call = await client.call(destinationNumber, {
    from: "+254700000001",  // one of your claimed Sautikit numbers
  });
 
  call.on("ringing",  () => updateUI("Ringing..."));
  call.on("answered", () => updateUI("Connected"));
  call.on("ended",    () => updateUI("Call ended"));
 
  return call;
}
 
// ── Inbound call ─────────────────────────────────────────────────
client.on("incoming", (inboundCall) => {
  showIncomingCallBanner(inboundCall.from);
 
  document.getElementById("accept-btn").onclick = () => inboundCall.accept();
  document.getElementById("reject-btn").onclick = () => inboundCall.reject();
 
  inboundCall.on("ended", () => hideIncomingCallBanner());
});
 
// ── Hang up ───────────────────────────────────────────────────────
function endCall(call) {
  call.hangup();
}

Minimal HTML click-to-call button

<!doctype html>
<html lang="en">
<head><title>Click to Call</title></head>
<body>
  <button id="call-btn">Call Support</button>
  <p id="status">Ready</p>
 
  <script type="module">
    import { SautikitClient } from "https://cdn.jsdelivr.net/npm/@sautikit/browser/dist/index.esm.js";
 
    const client = new SautikitClient({ tokenUrl: "/api/sip-token" });
    await client.connect();
 
    let activeCall = null;
 
    document.getElementById("call-btn").addEventListener("click", async () => {
      if (activeCall) {
        await activeCall.hangup();
        activeCall = null;
        document.getElementById("call-btn").textContent = "Call Support";
        document.getElementById("status").textContent = "Call ended";
        return;
      }
 
      activeCall = await client.call("+254700000001", { from: "+254700000001" });
      document.getElementById("call-btn").textContent = "Hang Up";
      activeCall.on("answered", () => {
        document.getElementById("status").textContent = "Connected";
      });
      activeCall.on("ended", () => {
        document.getElementById("status").textContent = "Call ended";
        document.getElementById("call-btn").textContent = "Call Support";
        activeCall = null;
      });
    });
  </script>
</body>
</html>

Pricing notes

Browser-originated calls are billed identically to API-originated calls at the standard outbound per-minute rate for the destination. There is no additional charge for the WebRTC or SIP gateway layer; the gateway cost is included in the per-minute rate.

Token minting (POST /v1/sip/token) does not incur a fee per request. A connected browser that places no calls costs nothing beyond the workspace subscription.

Key cost drivers:

  • Call duration: billed from the moment the called party answers to hangup.
  • Destination country: rates differ per country. Kenya mobile, Kenya landline, and international rates are listed on the pricing page.
  • Ring time before answer: if the destination rings but does not answer, you are billed for the ring duration. Keep the timeout on your Dial actions short if the user experience allows.

For call center agents handling many short calls, model cost per agent hour rather than per call. An agent handling 10 calls of 4 minutes each in an hour at the standard rate gives a predictable hourly cost that you can compare against traditional telephony line rental.

Next steps

  • WebRTC concept: SIP token internals, signing key rotation, and detailed security guidance.
  • SIP token API reference: POST /v1/sip/token request and response schema.
  • Calls concept: how CDRs are created for browser-originated calls.
  • Call Center use case: routing inbound calls to browser-based agents.
  • Numbers concept: claiming a number to use as the from identifier for outbound calls.