Browser Calling with WebRTC
How Sautikit's SIP token mint flow works for browser-based calling: token lifetime, SDK overview, and workspace attribution.
Sautikit supports browser-based calling by issuing short-lived SIP tokens from your server via POST /v1/sip/token. The token carries a workspace identity claim, is verified by the SIP gateway at WebSocket handshake, and attributes call billing to your workspace's KES wallet. Your client-side browser SDK uses the token to connect; your server renews it before expiry.
Your API key must never be exposed in a browser. Instead, your backend mints a short-lived SIP token and hands it to the browser at page load. The token proves to the SIP gateway that the browser session is authorized under a specific workspace without revealing the API key or any admin credentials.
curl -X POST "https://api.sautikit.com/v1/sip/token" \
-H "Authorization: Bearer $SAUTIKIT_API_KEY"{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": "2026-06-27T10:05:00Z"
}The token is a signed HS256 JWT with these claims:
{
"iss": "sautikit-sip",
"sub": "<workspace_uuid>",
"iat": 1751000000,
"exp": 1751000300
}Token TTL is 5 minutes. Refresh the token before it expires. The gateway accepts tokens signed by either the active signing key or the optional overlap key during rotation windows.
Your server mints the token on demand and returns it to authenticated browser sessions only. Never cache the same token across multiple users; each browser session should receive its own token.
// Express.js example (server side only)
app.get("/api/sip-token", requireAuth, async (req, res) => {
const resp = await fetch("https://api.sautikit.com/v1/sip/token", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.SAUTIKIT_API_KEY}` },
});
const { token, expires_at } = await resp.json();
res.json({ token, expires_at });
});The Sautikit browser SDK is a thin wrapper around a SIP WebSocket connection. It handles:
/api/sip-token endpoint)ringing, answered, ended)import { SautikitClient } from "@sautikit/browser";
const client = new SautikitClient({
// Your server endpoint that returns { token, expires_at }
tokenUrl: "/api/sip-token",
});
await client.connect();
// Place an outbound call
const call = await client.call("+254722000001", {
from: "+254700000001", // one of your claimed numbers
});
call.on("answered", () => console.log("connected"));
call.on("ended", () => console.log("call ended"));
// End the call
await call.hangup();The SDK fires call events that you can use to update your UI. Incoming calls are surfaced via the client.on("incoming", (call) => ...) event.
Because the SIP token embeds the workspace UUID as the sub claim, the gateway attributes every call placed through the browser to your workspace. Call cost is debited from your KES wallet at hangup, exactly as with API-originated calls. The call record appears in GET /v1/calls with direction: "outbound".
The signing key is configured via the SAUTIKIT_SIP_TOKEN_SIGNING_KEY environment variable on the backend. Two-key overlap rotation is supported:
SAUTIKIT_SIP_TOKEN_SIGNING_KEY): signs new tokens and verifies incoming tokens.SAUTIKIT_SIP_TOKEN_SIGNING_KEY_NEXT): verify-only, used during the rotation window.During rotation: set the new key as _NEXT, wait for all existing 5-minute tokens to expire, then promote _NEXT to active and clear _NEXT. The gateway accepts tokens from both keys throughout the window.
SAUTIKIT_SIP_TOKEN_SIGNING_KEY. All existing tokens are immediately invalidated.