Install peers, permissions, tokens, and place your first Alaznah 1:1 call.
Quick Start
End-to-end path from a blank React Native app to a 1:1 voice or video call with the built-in UI (CallingScreen).
This page is meant to work on its own. Deeper references: Installation · Permissions · Authentication · Call UI.
Before you start
| Requirement | Detail |
|---|---|
| React Native | 0.76+, Android compileSdk 35 or 36 — Compatibility |
| App type | RN CLI or Expo development build (not Expo Go) |
| Console | Project in the Developer Console |
| Devices | Two devices (or device + simulator where your setup allows) |
| Identities | Each device uses a different userId (e.g. alice and bob) |
Physical devices are strongly preferred for real mic/camera tests.
1. Install the SDK
Peers stay in your app (not bundled inside @alaznah/calling). Pick your package manager:
npm install @alaznah/calling
# Required peers
npm install react-native-webrtc react-native-incall-manager @react-native-community/netinfo react-native-svg| Manager | Notes |
|---|---|
| npm | Install @alaznah/calling, then run the Required peers command (npm tab only) |
| Yarn / pnpm | One command — package + peers together |
REQUIRED peers: react-native-webrtc, react-native-incall-manager, @react-native-community/netinfo, react-native-svg
OPTIONAL: react-native-callkeep — only if you explicitly choose CallKeep. The SDK’s own native calling path does not require it.
Without react-native-webrtc, the first startCall fails with WebRTC adapters not found.
Package: @alaznah/calling on npm. Full install notes: Installation.
2. Native permissions
iOS — Info.plist
Edit ios/<AppName>/Info.plist (or Xcode → Info):
<key>NSMicrophoneUsageDescription</key>
<string>Microphone access is required for audio and video calls.</string>
<!-- Required for video calls -->
<key>NSCameraUsageDescription</key>
<string>Camera access is required for video calls.</string>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>voip</string>
<string>remote-notification</string>
</array>Then:
cd ios && pod install && cd ..Android — AndroidManifest.xml
Inside <manifest> (before <application>):
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<!-- Video (or apps that offer both voice and video) -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />Voice-only vs video matrices: Permissions.
Expo (development build)
Expo Go is not supported — you need a development build (or bare workflow after prebuild).
In app.json / app.config:
{
"expo": {
"plugins": ["@alaznah/calling"]
}
}Then generate native projects (or use EAS):
npx expo prebuild
# or
eas build --profile developmentAfter prebuild, confirm the generated Info.plist and AndroidManifest.xml still include the keys/permissions above (edit if the plugin did not inject them). Full Expo notes: Installation.
Rebuild
Metro reload is not enough after install or plist/manifest changes:
# React Native CLI
npx react-native run-ios
npx react-native run-android
# Expo development build
npx expo run:ios
npx expo run:android3. Get a calling token
The SDK needs a short-lived Alaznah calling JWT. That is not your app’s login cookie.
Two tokens (read this once)
| Name | Source | Used by |
|---|---|---|
sessionToken | Your login (Supabase / Firebase / your JWT / SSO session). Alaznah does not issue this. | App → your backend Authorization: Bearer … |
| Calling token | Minted by your backend (or Console Test Token for demos) | Returned from getAuthToken() → SDK → signaling |
Full walkthrough (diagram, sample backend route, mint HTTP): Authentication.
Option A — Console Test Token (fastest for this guide)
No backend yet:
- Open Console → Tokens (or Tokens in the developer console).
- Select your project.
- Enter
userId(e.g.aliceon device A,bobon device B). - Generate → Copy token.
Paste it into getAuthToken for local demos only (tokens expire — mint again when they do).
Option B — Your backend (production)
Mobile app calls your API (example path — you choose the URL):
POST https://api.yourapp.com/calling/token
Authorization: Bearer <sessionToken>sessionToken= whatever your app already has after login.- Your server validates that session, then calls Alaznah:
POST https://www.alaznah.com/api/v1/calling/token
Authorization: Bearer ac_live_YOUR_API_KEY
Content-Type: application/json
{ "userId": "alice", "ttlSeconds": 900 }Your API returns { "token": "<calling-jwt>" } to the app. Never put ac_live_… in the client.
Step-by-step integration + pseudo-code: Authentication.
userId rules
| Rule | Why |
|---|---|
| Stable per end user | Peers dial this id |
| Must match the token claim | Mismatch → auth / connect failures |
| Different on each test device | Same JWT / same userId on both phones → the other side never rings correctly |
4. Minimal app — provider + call UI
Pick one:
| Path | Mount | Best for |
|---|---|---|
A — CallingScreen | Built-in dialer + incoming + active | First call / demos |
B — CallingUI | Your screens + Alaznah overlay | Real chat / contacts apps |
CallingUI is exported from @alaznah/calling. Advanced pieces (IncomingCallScreen, theme helpers, etc.) stay on @alaznah/calling/ui. More detail: Call UI.
Path A — CallingScreen
import React from 'react';
import {
CallingProvider,
CallingScreen,
DEFAULT_HOSTED_SIGNALING_URL,
} from '@alaznah/calling';
// Device A: 'alice' / Device B: 'bob' — must match the token you minted
const APP_USER_ID = 'alice';
const DEFAULT_PEER_ID = 'bob';
async function fetchCallingToken(): Promise<string> {
// Demo: return a Console Test Token minted for APP_USER_ID
// return 'eyJ...';
// Production: sessionToken comes from YOUR app login — see Authentication docs
const res = await fetch('https://api.yourapp.com/calling/token', {
headers: { Authorization: `Bearer ${sessionToken}` },
});
if (!res.ok) throw new Error('Failed to mint calling token');
const { token } = await res.json();
return token;
}
export default function App() {
return (
<CallingProvider
config={{
userId: APP_USER_ID,
getAuthToken: fetchCallingToken,
// Hosted Signaling is the default — omit signalingUrl, or set explicitly:
signalingUrl: DEFAULT_HOSTED_SIGNALING_URL, // wss://signal.alaznah.com
}}
>
<CallingScreen
userId={APP_USER_ID}
defaultPeerId={DEFAULT_PEER_ID}
onError={(e) => console.warn('calling', e)}
/>
</CallingProvider>
);
}Path B — your app + CallingUI overlay (recommended)
Wrap the navigator in CallingProvider, mount CallingUI once, then call client.startCall(...) from chat/contacts.
import React from 'react';
import { Button } from 'react-native';
import {
CallingProvider,
CallingUI,
useCallingClient,
useCallingReady,
requestCallPermissions,
DEFAULT_HOSTED_SIGNALING_URL,
} from '@alaznah/calling';
const APP_USER_ID = 'alice';
async function fetchCallingToken(): Promise<string> {
// sessionToken = YOUR app login token — see Authentication docs
const res = await fetch('https://api.yourapp.com/calling/token', {
headers: { Authorization: `Bearer ${sessionToken}` },
});
if (!res.ok) throw new Error('Failed to mint calling token');
const { token } = await res.json();
return token;
}
function CallingOverlay() {
const client = useCallingClient();
return (
<CallingUI
client={client}
onError={(e) => console.warn('calling', e)}
/>
);
}
function ChatHeader({ peerId }: { peerId: string }) {
const client = useCallingClient();
const ready = useCallingReady();
const call = async (mediaType: 'audio' | 'video') => {
if (!ready) return;
await requestCallPermissions(mediaType);
await client.startCall({ calleeId: peerId, mediaType });
};
return (
<>
<Button title="Audio" disabled={!ready} onPress={() => void call('audio')} />
<Button title="Video" disabled={!ready} onPress={() => void call('video')} />
</>
);
}
export default function App() {
return (
<CallingProvider
config={{
userId: APP_USER_ID,
getAuthToken: fetchCallingToken,
signalingUrl: DEFAULT_HOSTED_SIGNALING_URL,
}}
>
{/* Your navigator / chat screens */}
<ChatHeader peerId="bob" />
<CallingOverlay />
</CallingProvider>
);
}Without CallingUI (or CallingScreen), startCall may connect with no visible UI.
Signaling
| Setup | Config |
|---|---|
| Hosted (recommended) | Omit signalingUrl, or set DEFAULT_HOSTED_SIGNALING_URL (wss://signal.alaznah.com) |
| Self-hosted | Set signalingUrl to your WSS endpoint — Self-hosting |
Do not point a physical device at your laptop’s localhost without a tunnel.
Permissions before dialing
API is a string, not { video: boolean }:
import { requestCallPermissions } from '@alaznah/calling';
await requestCallPermissions('audio'); // voice call
await requestCallPermissions('video'); // video call (mic + camera)Call the matching mode before client.startCall(...) (Path B). CallingScreen (Path A) should do the same in its dial actions.
5. Place a call
On device A (userId: alice)
- Wait until the client is connected (valid token + signaling).
- Dial peer id
bob(Path A dialer, or Path B audio/video buttons). - Choose audio or video.
On device B (userId: bob)
- Same project / hosted signaling.
- Token minted for
bob(not alice’s token). - Incoming screen appears.
Accept / decline
| Action | How |
|---|---|
| Accept | Swipe up on the green control — tap does not accept |
| Decline | Red decline control |
Foreground calling works once both clients are online with valid tokens. Background / kill-state ringing is a later step (below).
6. Verify checklist
Use this before filing a bug:
-
@alaznah/callingand required peers installed (react-native-webrtc,react-native-incall-manager,@react-native-community/netinfo,react-native-svg) -
pod install(iOS) and a native rebuild after install / plist / manifest - Mic (and camera for video) usage strings / manifest permissions present
- Device A and B have different
userIds matching their tokens - Tokens not expired;
getAuthTokenreturns a JWT for thatuserId - Both devices can reach Hosted Signaling (or your
signalingUrl) - Accept uses swipe up, not tap
- Testing on a physical device when media fails on simulator
7. Common failures
| Symptom | Fix |
|---|---|
WebRTC adapters not found | Install peers — step 1 · Installation |
must be used within CallingProvider | Wrap the tree that uses calling hooks/UI |
| Auth / 401 / reconnect exhausted | Fresh token; userId matches JWT; Authentication |
startCall connects but no UI | You must mount CallingScreen or CallingUI — Call UI |
| Accept “does nothing” | Swipe up — Incoming Calls |
| Android AAR / compileSdk 36 forced | Compatibility |
| No ring when app is killed | Push not configured yet — optional step below |
More: Troubleshooting.
8. (Optional) Background ringing
Skip for the first successful foreground call. When you need ring-in-background / kill-state:
- Configure push credentials in the console
- Follow Push Notifications and Incoming Calls
Next
| Goal | Guide |
|---|---|
| Chat app + overlay UI | Call UI — Path B (CallingUI) |
| Tokens in depth | Authentication |
| Voice-only / video guides | Voice Calls · Video Calls |
| Themes / custom screens | Customization · UI Components |
| Versions | Compatibility |