Android quickstart

Last updated  Sep 9, 2026

Add the Maven repository, configure the SDK once at launch, implement one delegate, and present the assistant from any button.

Install the SDK

The SDK ships as a binary from a public, token-free Maven repository. Add the repository, then the dependency.

settings.gradle.kts
dependencyResolutionManagement {    repositories {        google()        mavenCentral()        // Voqal SDK — public, binary-only Maven repo (no token required).        maven { url = uri("https://raw.githubusercontent.com/VoqalAI/voqal-android-maven/main") }    }}
app/build.gradle.kts
dependencies {    implementation("ai.voqal:voqal-sdk:2.1.0")}

Configure at launch

Call VoqalSDK.setup once in your Application. Calling prewarm right after opens the engine connection in the background so the first turn skips a cold handshake.

App.kt
import android.app.Applicationimport ai.voqal.sdk.*class App : Application() {    val voqalDelegate = VoqalCredentials(authStore)    override fun onCreate() {        super.onCreate()        VoqalSDK.setup(            VoqalConfiguration(                requestId = "prod-yourapp",            // "prod-" / "stg-"                apiKey = "pk_live_…",                  // sent as X-Voqal-Key                theme = VoqalTheme(accent = "#2d5bff", appearance = VoqalTheme.Appearance.AUTO),            )        )        VoqalSDK.prewarm(voqalDelegate)   // warm the engine at launch    }}
requestId selects the environment by prefix: prod- routes to production, stg- to staging.

Implement the delegate

VoqalDelegate supplies credentials live — the SDK reads the token on every request, so always return one that is currently valid. getForwardedHeaders and onActionButtonTapped are new in 2.0.0 and both optional; the two sections below cover them.

VoqalCredentials.kt
import ai.voqal.sdk.VoqalDelegateclass VoqalCredentials(private val authStore: AuthStore) : VoqalDelegate {    // Read live on every request — always hand back a current token.    override fun getToken(): String = authStore.currentToken    // Optional JSON: country, user id — read by the engine as X-Client-Metadata.    override fun getMetadata(): String? =        """{"country_code":"EGY","user_id":"285"}"""    // New in 2.0.0 — forwarded verbatim to your backend (see below).    override fun getForwardedHeaders(): Map<String, String> =        mapOf("X-Tenant-Id" to authStore.tenantId)    // Optional — host navigates when the action button is tapped (see below).    override fun onActionButtonTapped() { openCheckout() }    override fun onUploadResult(result: String) {}   // recording lifecycle    override fun onError(error: Throwable) {}}

Forward headers to your backend

New in 2.0.0 Return extra headers from getForwardedHeaders() and the SDK sends each one namespaced as X-Voqal-Forward-<name>. The engine strips the prefix and forwards the value verbatimto your tenant’s backend — on both HTTP turns and the voice WebSocket, with no dashboard configuration.

VoqalCredentials.kt
// Read live per turn — rotating a value takes effect on the next request.override fun getForwardedHeaders(): Map<String, String> = mapOf(    "X-Tenant-Id" to session.tenantId,           // → X-Voqal-Forward-X-Tenant-Id    "Authorization" to "Bearer ${session.backendToken}",   // reaches your backend as-is)
  • Read live on every request (like getToken), so rotating a value takes effect on the next turn.
  • Authorization is forwardable — it rides as X-Voqal-Forward-Authorization and reaches your backend intact.
  • Reserved control headers are dropped (x-token, x-voqal-key, x-request-id, x-client-metadata, content-type, accept) so a forwarded key can never displace Voqal’s own.
  • At most 32 headers are forwarded; any beyond that are dropped, not errored.
getMetadata() and getForwardedHeaders() are different channels. Metadata is sent as X-Client-Metadata and read by the Voqal engine to route region and personalize; forwarded headers are passed through to your backend and never interpreted by the engine.

Present the assistant

Open the assistant from any of your own buttons — Views or Compose.

MainActivity.kt
// From any Activity — Views:assistantButton.setOnClickListener {    VoqalSDK.present(this, (application as App).voqalDelegate)}// Or from Compose:Button(onClick = { VoqalSDK.present(activity, delegate) }) { Text("Talk to Voqal") }
  • Voice, transcription, widgets, and confirmations are handled inside the sheet — your app ships no assistant UI.
  • Theming, presentation style, header branding, and the action button share one model across platforms — see Configuration.

Your own button

There is no Voqal launcher view on Android. Any click target you already have — a FloatingActionButton, a toolbar action, a list row, a notification, a deep link — calls VoqalSDK.present(activity, delegate). The activity must be a ComponentActivity (every AppCompat and Compose activity is).

HomeScreen.kt
// Compose: a floating action button. The SDK ships no launcher view; this is yours.Scaffold(    floatingActionButton = {        ExtendedFloatingActionButton(            onClick = { VoqalSDK.present(activity, delegate) },            icon = { Icon(Icons.Default.Mic, contentDescription = null) },            text = { Text("Ask") },        )    },) { … }

The assistant opens as a draggable bottom sheet over a scrim by default. For an opaque, edge-to-edge surface that closes only from its own button, set presentationStyle on the configuration — a setup-time setting, not a per-call one.

App.kt
// Setup-time setting: apply before present().VoqalSDK.setup(    VoqalConfiguration(        requestId = "prod-yourtenant",        apiKey = "pk_live_…",        presentationStyle = VoqalConfiguration.PresentationStyle.FULL_SCREEN, // default: SHEET    ),)

Redirect & checkout

When a turn needs to hand the user off to your own screen — a checkout, a receipt, an external payment page — Voqal gives you three paths, from most to least host-driven.

1. Action button. Set actionButton on the config to draw a call-to-action next to the voice dock. Tapping it fires onActionButtonTapped(), where your app navigates wherever it wants. It is off by default — leave actionButton null and nothing is drawn beside the voice wave.

App.kt
// App.kt — opt in to the dock's call-to-action button.VoqalConfiguration(    requestId = "prod-yourapp",    actionButton = VoqalActionButton(contentDescription = "Checkout"),)
VoqalCredentials.kt
import android.content.Contextimport android.content.Intentclass VoqalCredentials(private val appContext: Context) : VoqalDelegate {    // Do NOT keep an Activity in the delegate — it outlives the screen and leaks.    // Navigate from applicationContext with FLAG_ACTIVITY_NEW_TASK.    override fun onActionButtonTapped() {        val intent = Intent(appContext, CheckoutActivity::class.java)            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)        appContext.startActivity(intent)    }}
The SDK retains the delegate for the conversation’s lifetime, so a delegate must never hold a strong reference to an Activity — it would leak. Navigate from applicationContext with an Intent flagged FLAG_ACTIVITY_NEW_TASK.

2. Confirm widget.For an in-conversation approval — “Pay 250 EGP now?” — the engine emits a confirm widget and the SDK renders the approve/cancel step inside the sheet. Your app writes no UI; the confirmed action runs on the engine.

3. Automatic link cards. When the engine returns a link or redirect widget (a payment link, an invoice), the SDK opens it for you with an ACTION_VIEW intent — no host code required. The equivalent your app would otherwise write:

(handled by the SDK)
// The SDK opens engine link/redirect widgets for you — equivalent to:val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)context.startActivity(intent)
© 2026 VoqalVoqal SDK & engine documentation