Kallo SDK documentation
Add native SIP voice calling to your Flutter Android & iOS app — install, quickstart, and the full API reference.
Overview
Kallo SDK wraps the battle-tested baresip stack behind a small, clean Dart API so you can add real phone-grade voice calling — registration, inbound and outbound calls, mute, hold, and DTMF — to your Flutter app without writing native code. It ships as a closed binary; no C or native source is included.
- ✓Native SIP registration over TLS
- ✓Outbound and inbound calls
- ✓Mute, hold, and resume
- ✓DTMF tone sending
- ✓Speaker / earpiece routing
- ✓SRTP-encrypted media by default
- ✓Opus + G.711 audio codecs
- ✓Runtime license validation
Android (minimum API 28 / Android 9) and iOS (13+) are both supported — one Flutter package; Flutter builds the right half per target.
Requirements
- •Flutter 3.10+ (Dart 3.0+).
- •Android minSdk 28 (Android 9) or higher — the native plugin declares minSdk 28; lower values fail the build.
- •Android: the RECORD_AUDIO runtime permission must be requested and granted before placing or accepting a call.
- •iOS 13+ — add NSMicrophoneUsageDescription to your app's Info.plist. The SDK requests the microphone at call time, so do NOT pre-request it in the app (a host-app pre-request can block the call).
Quickstart
Add the SDK to your app's pubspec.yaml (use the path or git URL provided with your license), then run flutter pub get.
dependencies:
kallo_sdk:
path: ../kallo-sdk # or the git: URL provided with your licenseAdd the required permissions to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />Set minSdk to 28 (or higher) in android/app/build.gradle.kts:
android {
defaultConfig {
minSdk = 28 // Kallo requires API 28+ (Android 9)
}
}A working call
A complete flow: initialize, register, wait until registered, place a call, follow its state, and hang up. This uses the current instance-based API (KalloClient.instance).
import 'package:kallo_sdk/kallo_sdk.dart';
Future<void> demo() async {
// 1. Initialize — validates your license, then configures the SIP stack.
await KalloClient.instance.init(
KalloConfig(
domain: 'sip.example.com',
username: '1001',
password: 'secret',
licenseKey: 'YOUR-LICENSE-KEY',
// licenseServerUrl defaults to https://kallo-api.up.railway.app
),
);
// 2. Listen for registration state changes.
KalloClient.instance.registrationStream.listen((state) {
print('registration: $state');
});
// 3. Register, then wait until we are registered.
await KalloClient.instance.register();
await KalloClient.instance.registrationStream
.firstWhere((s) => s == KalloRegistrationState.registered);
// 4. Place an outbound call and follow its state.
final call = await KalloClient.instance.calls.makeCall('1002');
call.stateStream.listen((state) {
print('call: $state');
});
// 5. Hang up later.
await call.hangup();
}Receiving inbound calls
// Subscribing eagerly attaches the native listener, so inbound
// calls are not dropped before you handle them.
KalloClient.instance.calls.incomingCalls.listen((call) async {
await call.accept(); // or: await call.reject();
});Handling errors
Wrap init / register in try/catch. Catch KalloLicenseException for license problems, or the KalloException base type for any SDK failure.
try {
await KalloClient.instance.init(config);
} on KalloLicenseException catch (e) {
print('license problem: ${e.message}');
} on KalloException catch (e) {
print('SDK error: ${e.message}');
}Start with your AI assistant
In a hurry? Copy the prompt below and paste it into your AI coding assistant (Cursor, Claude Code, or similar) to scaffold a working Kallo SDK integration in your Flutter app. It already includes the key facts and the correct instance-based API; the agent will read the rest of this page for detail and ask you for your SIP credentials and license key.
Integrate the Kallo SDK (native SIP voice calling) into my Flutter app (Android & iOS).
About the SDK:
- Android (minSdk 28 / Android 9) and iOS (13+). One Flutter plugin package
contains both platforms — Flutter builds the right half per target.
- Flutter 3.10+ / Dart 3.0+.
- Distributed as a closed-binary Flutter plugin package (the kallo_sdk Dart API
plus prebuilt native binaries: an Android .aar and an iOS xcframework), NOT on
pub.dev. Add it as a path: (or private git) dependency on the kallo_sdk package
in pubspec.yaml — do not try to fetch it from pub.dev, and do not add the raw
.aar/xcframework to the native projects directly.
- Android: requires the RECORD_AUDIO permission (plus INTERNET and
MODIFY_AUDIO_SETTINGS) in AndroidManifest.xml, and the RECORD_AUDIO runtime
permission must be requested and granted before placing or accepting a call.
- iOS: add NSMicrophoneUsageDescription to Info.plist. The SDK requests the mic
at call time — do NOT pre-request microphone permission in the app (a host-app
pre-request can block the call). Run pod install after flutter pub get.
Use the instance-based API (KalloClient.instance — NOT a static/global API):
import 'package:kallo_sdk/kallo_sdk.dart';
// 1. Initialize — validates the license, then configures the SIP stack.
await KalloClient.instance.init(
KalloConfig(
domain: '<SIP domain>',
username: '<SIP username>',
password: '<SIP password>',
licenseKey: '<Kallo license key>',
),
);
// 2. Listen to registration state.
KalloClient.instance.registrationStream.listen((state) {
// state is a KalloRegistrationState:
// none | registering | registered | failed | expired
});
// 3. Register and wait until registered.
await KalloClient.instance.register();
await KalloClient.instance.registrationStream
.firstWhere((s) => s == KalloRegistrationState.registered);
// 4. Place an outbound call and follow its state.
final call = await KalloClient.instance.calls.makeCall('<destination>');
call.stateStream.listen((state) {
// state is a KalloCallState:
// idle | calling | ringing | active | held | ended | failed
});
// 5. Hang up.
await call.hangup();
Build a minimal call screen (single StatefulWidget is fine) that:
- Calls init(...) then register() on start, and shows the current registration
state in the UI.
- Has a text field for the destination extension/number and a "Call" button
that invokes KalloClient.instance.calls.makeCall(<destination>).
- Shows the live call state from the returned call's stateStream.
- Has a "Hang up" button that calls call.hangup().
- Wraps init / register / makeCall in try/catch with error handling on the
Kallo exception types: catch KalloLicenseException (bad/expired license),
KalloRegistrationException (registration failed), KalloCallException (call
failed), and KalloNetworkException, falling back to the KalloException base
type. Show a readable message from e.message.
Important:
- Do NOT hardcode credentials. Ask me for my SIP domain, username, password,
and Kallo license key, and wire them in from config / secure storage.
- Do NOT invent SDK methods. The full API reference (KalloConfig fields,
KalloClient / KalloCall / KalloCallManager members, enums, exceptions) and
install steps are on this same docs page — read it for anything not shown
above, and ask me if a signature is unclear rather than guessing.API reference
KalloConfig
Immutable configuration passed to KalloClient.instance.init(). Throws ArgumentError on an empty domain/username or an out-of-range port.
| Field | Type | Description |
|---|---|---|
| domain | String | SIP domain / registrar host (required). |
| username | String | SIP account username / extension (required). |
| password | String | SIP account password (required). |
| licenseKey | String | Commercial license key issued for your app (required). |
| displayName | String? | Optional display name shown to the remote party. |
| useTLS | bool = true | Use TLS transport for SIP signalling (default true, port 5061). |
| useSRTP | bool = true | Use SRTP for media encryption (default true). |
| stunServer | String? | Optional STUN server URI for NAT traversal. |
| caBundlePath | String? | Path to a PEM CA bundle to verify the server's TLS certificate (for a private CA or pinned cert). |
| tlsAcceptSelfSigned | bool = false | Accept the server's TLS certificate without verification — testing only; never enable in production. |
| port | int? | Signalling port. Defaults to 5061 (TLS) or 5060 (UDP). |
| licenseServerUrl | String | Base URL of the Kallo license backend; override only for self-hosted validators. |
KalloClient
The shared SIP client, accessed via KalloClient.instance. forTesting() builds an isolated instance for unit tests.
KalloClient.instanceinit(KalloConfig config, {String? appId, http.Client? httpClient})register()unregister()dispose()registrationStreamcurrentRegistrationStatecallsKalloCall
Represents a single call. Returned by makeCall and emitted on incomingCalls.
callIdremoteUridirectionaccept()reject()hangup()setMuted(bool muted)setHold(bool onHold)setSpeaker(bool speaker)sendDtmf(String digit)stateStreamcurrentStateisMutedisOnHoldKalloCallManager
Manages calls, reachable via KalloClient.instance.calls (also KalloCallManager.instance).
makeCall(String destination)incomingCallsactiveCallEnums
Registration lifecycle states emitted on registrationStream.
Per-call lifecycle states emitted on a call's stateStream. ended and failed are terminal.
Whether a call was placed by this device (outbound) or received (inbound).
Exceptions
All SDK errors extend KalloException. Catch the base type to handle any failure, or a subtype for finer control.
KalloExceptionBase type for every error thrown by the SDK.KalloLicenseExceptionLicense validation failed or could not be completed.KalloRegistrationExceptionSIP registration failed (bad credentials, unreachable server, or timeout).KalloCallExceptionA call operation failed (place, accept, reject, hangup, or an in-call control).KalloNetworkExceptionA transport/network-level failure talking to native or the license backend.Get the SDK
Request access, then download the closed-binary SDK from your dashboard once you have an active license.