1. Quickstart (5 min)
Three steps from zero to a working video call on your site.
Step 1 — Get API keys
Email partners@tkawen.com with your site URL to receive your pk_live_… + sk_live_… pair.
Step 2 — Server: exchange identity for SDK token
On every page load (your server-side), sign your user identity with your sk_live_ and exchange for a short-lived (1h) browser-safe token. Never expose sk_live_ to the browser.
php// PHP example $pk = 'pk_live_xxxxx'; $sk = 'sk_live_xxxxx'; $user = ['email' => 'jane@yoursite.com', 'name' => 'Jane Doe']; $identity = base64_encode(json_encode([ 'email' => $user['email'], 'name' => $user['name'], 'ts' => time(), ])); $signature = hash_hmac('sha256', $identity, $sk); $ch = curl_init('https://liqaa.io/api/public/v1/sdk-token'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $sk, 'Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode([ 'public_key' => $pk, 'identity_base64' => $identity, 'signature' => $signature, ]), ]); $res = json_decode(curl_exec($ch), true); $sdkToken = $res['sdk_token'];
javascript// Node.js example const crypto = require('crypto'); const fetch = require('node-fetch'); const pk = 'pk_live_xxxxx'; const sk = 'sk_live_xxxxx'; const identity = Buffer.from(JSON.stringify({ email: user.email, name: user.name, ts: Math.floor(Date.now()/1000), })).toString('base64'); const signature = crypto.createHmac('sha256', sk).update(identity).digest('hex'); const r = await fetch('https://liqaa.io/api/public/v1/sdk-token', { method: 'POST', headers: { Authorization: 'Bearer ' + sk, 'Content-Type': 'application/json' }, body: JSON.stringify({ public_key: pk, identity_base64: identity, signature }), }); const { sdk_token } = await r.json();
Step 3 — Browser: choose your install
Option A — npm (TypeScript projects, React, bundlers):
bashnpm install @liqaa/js
javascriptimport { LIQAA } from '@liqaa/js'; const liqaa = await LIQAA.init({ publicKey: 'pk_live_xxxxx', sdkToken: sdkToken, // from your server accent: '#1d4ed8', }); document.querySelector('#call-btn')!.addEventListener('click', () => { liqaa.startCall('support@yoursite.com', 'Support'); });
React? Use @liqaa/react for hooks + components:
bashnpm install @liqaa/react @liqaa/js
tsximport { LIQAAProvider, LIQAACallButton } from '@liqaa/react'; <LIQAAProvider publicKey={pk} sdkToken={token}> <LIQAACallButton email="support@yoursite.com"> Talk to Support </LIQAACallButton> </LIQAAProvider>
Option B — drop-in script (zero install, plain HTML / WordPress / etc.):
html<!-- Anywhere in your HTML --> <script src="https://liqaa.io/sdk.js" data-key="pk_live_xxxxx" data-token="<%= sdkToken %>" data-accent="#1d4ed8" data-position="right"></script> <!-- Trigger anywhere on your page --> <button onclick="LIQAA.startCall('support@yoursite.com', 'Support')"> Talk to Support </button>
Either way, a floating bubble appears at the bottom-right (or bottom-left for RTL). Clicking your button starts a video call inside your site (no redirect).
2. JS SDK Reference
| Method | Description |
|---|---|
LIQAA.toggle() | Show or hide the bubble panel. |
LIQAA.show() | Open the panel programmatically. |
LIQAA.hide() | Close the panel programmatically. |
LIQAA.openConversationWith(email, name?) | Open chat with another user (creates persistent room). |
LIQAA.startCall(email, name?) | Start an instant video call with another user (iframe inside bubble). |
Script-tag attributes
| Attribute | Default | Description |
|---|---|---|
data-key | — | Required. Your pk_live_… |
data-token | — | Required. Browser-safe SDK JWT (from /sdk-token). |
data-accent | #1d4ed8 | Bubble + button color (any hex). |
data-position | right | right or left. |
data-rtl | auto | Force RTL (true/false) or auto-detect. |
3. REST API
Direct API access for server-to-server integrations. Authenticate with Authorization: Bearer sk_live_…
| Endpoint | Description |
|---|---|
POST /api/public/v1/conversations | Create or reuse persistent room for a (caller, callee, conv_id) tuple. |
GET /api/public/v1/conversations/:id | Fetch room state by external_conversation_id. |
DELETE /api/public/v1/conversations/:id | End an active call. |
POST /api/public/v1/sdk-token | Exchange identity + sk for short-lived browser SDK token. |
POST /api/public/v1/webhooks | Subscribe to event webhooks (returns signing secret). |
GET /api/public/v1/webhooks | List your subscriptions. |
GET /api/public/v1/webhooks/:id/deliveries | Recent delivery audit. |
DELETE /api/public/v1/webhooks/:id | Cancel a subscription. |
Example: create a call from server
bashcurl -X POST https://liqaa.io/api/public/v1/conversations \ -H "Authorization: Bearer sk_live_xxxxx" \ -H "Content-Type: application/json" \ -d '{ "caller_email": "agent@yoursite.com", "caller_name": "Support Agent", "callee_email": "customer@example.com", "callee_name": "Customer X", "external_conversation_id": "ticket-42" }' # Response (201 created or 200 if reused): { "ok": true, "reused": false, "room_name": "room-abc123", "join_url": "https://liqaa.io/meeting/room-abc123", "embed_url": "https://liqaa.io/embed/room-abc123", "expires_at": "2026-04-30T18:00:00+00:00" }
4. Webhooks
Subscribe to events to keep your app in sync. We sign every payload with your signing_secret(HMAC-SHA256). Failed deliveries retry 5 times with exponential backoff (5m, 30m, 2h, 6h, 24h).
Available events
call.started— fires when a new room is created.call.ended— fires when a room is ended (DELETE or LiveKit timeout).call.declined— fires when the recipient declines an incoming call.message.sent— fires when a message is sent (coming soon).conversation.created— fires when a new persistent conversation is opened.*— wildcard, subscribe to all events.
Subscribe
bashcurl -X POST https://liqaa.io/api/public/v1/webhooks \ -H "Authorization: Bearer sk_live_xxxxx" \ -H "Content-Type: application/json" \ -d '{ "url": "https://yoursite.com/api/webhooks/liqaa", "events": ["call.started","call.ended"], "description": "Production endpoint" }' # Response (201 created): { "ok": true, "data": { "id": 17, "url": "...", "events": [...], "active": true, ... }, "signing_secret": "whsec_<save-this-now-shown-only-once>" }
Verify signatures
We send X-LIQAA-Signature: t=<timestamp>,v1=<hmac> on every webhook. Recompute and compare with constant-time equality.
php// PHP — verify webhook $payload = file_get_contents('php://input'); $header = $_SERVER['HTTP_X_LIQAA_SIGNATURE'] ?? ''; parse_str(strtr($header, ',', '&'), $parts); $timestamp = $parts['t'] ?? ''; $received = $parts['v1'] ?? ''; $expected = hash_hmac('sha256', $timestamp . '.' . $payload, $signingSecret); if (!hash_equals($expected, $received)) { http_response_code(401); exit; } // Reject replays older than 5 minutes if (abs(time() - (int) $timestamp) > 300) { http_response_code(401); exit; } $event = json_decode($payload, true); // $event['event'], $event['data'], ...
Why LIQAA?
- Modern infrastructure — globally distributed, low-latency, production-ready.
- Transparent pricing — pay-as-you-grow, free tier forever, no surprises.
- 🔁 Persistent rooms — same conversation = same room across calls (no dashboard pollution).
- Multi-language — full RTL support out of the box. English, Arabic, French, more.
- LiveKit SFU — battle-tested WebRTC under the hood.
- 🔌 Drop-in — one script tag, ten minutes to integrate.
- 🔒 Stripe-style security — pk/sk separation, signed webhooks, HMAC.