Flash Call Verification Sdk

What is Flash Call Verification?

Flash Call Verification is a seamless, cost-effective, and highly secure alternative to traditional SMS OTPs (One-Time Passwords). Instead of sending an SMS text message containing a code that the user must wait for and manually type, the Kasookoo Flash Call system automatically verifies a user's mobile phone number by placing a brief, missed call (a "flash call") to the user's device.

Key Benefits

  • Zero Friction: Users do not need to switch apps, read text messages, or manually type 6-digit codes.
  • High Speed & Conversion: Verification completes within seconds as soon as the test call reaches the device.
  • Cost Efficiency: Reduces delivery costs compared to international SMS OTP gateway rates.
  • Enhanced Security: Eliminates risks associated with SMS interception, SIM swapping, and human input errors.

How Flash Call Verification Works (Detailed Workflow)

[ User Device ]                 [ Partner App / SDK ]                 [ Kasookoo Platform ]
       |                                  |                                     |
       |--- 1. Enters Phone Number ------>|                                     |
       |                                  |--- 2. Request Flash Call Session -->|
       |                                  |<-- 3. Returns Session ID & Target --|
       |                                  |                                     |
       |<== 4. Incoming Missed Call ============================================|
       |    (Detects Calling Line ID)     |                                     |
       |                                  |                                     |
       |                                  |--- 5. Match Found & Verify CLI ---->|
       |                                  |<-- 6. Verification Confirmed ------|

Step-by-Step Flow:

  1. User Initiates Verification:
    The user enters their mobile phone number into the mobile application and taps the "Verify" button.

  2. Session Creation:
    The application communicates with the Kasookoo backend platform to request a new verification session for the given phone number.

  3. Target Assignment:
    The platform creates a session and returns details including a unique session identifier and the designated target caller number (CLI) from which the verification call will originate.

  4. Outbound Test Call:
    The Kasookoo platform places a short test call to the user's mobile number. The call rings briefly and is automatically disconnected before the user can answer, ensuring no charges are incurred by the user.

  5. Automated Call Detection:
    The embedded SDK monitors incoming calls on the user's device in real-time. When the call arrives, the SDK automatically reads the caller ID without answering the call.

  6. Automatic Number Matching & Confirmation:
    The SDK compares the incoming caller ID with the target caller ID assigned to the session. Upon a successful match, the SDK confirms the verification with the platform, completing the phone number verification instantly.


SDK Integration Guide

This guide provides step-by-step instructions on how to integrate and use the sdk-release.aar in your Android project.


Step 1: Add Permissions to Manifest

The SDK requires telephony permissions to detect incoming flash calls and verify the phone number. Add the following permissions to your AndroidManifest.xml inside the <manifest> tag:

<!-- Required for Internet access -->
<uses-permission android:name="android.permission.INTERNET" />

<!-- Required for Flash Call Verification -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />

Step 2: Import the .aar File

  1. Locate your sdk-release.aar file.
  2. Copy the file into your app module's libs directory (e.g., app/libs/).
  3. If the libs folder doesn't exist, create it.

Step 3: Configure build.gradle

Open your app-level build.gradle (or build.gradle.kts) and add the AAR as a dependency.

Using Kotlin DSL (build.gradle.kts):

android {
    // ... other config
    
    buildFeatures {
        viewBinding = true
    }
}

dependencies {
    // Import the local AAR file
    implementation(files("libs/sdk-release.aar"))
    
    // Required to prevent OkHttp version mismatch crashes
    implementation(platform("com.squareup.okhttp3:okhttp-bom:4.12.0"))
    implementation("com.squareup.okhttp3:okhttp")
    implementation("com.squareup.okhttp3:logging-interceptor")

    // The SDK requires these transitively (ensure they are present)
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.10.0")
    implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.10.0")
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")
}

Step 5: Create the Verification Activity & VoLTE Network Settings

Create a new Activity (e.g., FlashCallActivity) to host the verification UI. Below is the standard implementation using the SDK's FlashCallManager.


Why & How to Use openNetworkSettings()

Why to Use openNetworkSettings():

  • VoLTE & Mobile Data Requirement: Flash Call verification requires an active internet connection while detecting incoming calls. If VoLTE is disabled, the device drops to a 2G/3G voice network during incoming calls, which disconnects 4G/5G mobile data and causes verification to fail or time out.
  • Seamless Navigation: Calling flashCallManager.openNetworkSettings() takes the user directly to their device's Mobile Network Settings so they can enable VoLTE / Mobile Data.

Why VoLTE is Required: Flash Call verification requires an active internet connection while detecting incoming calls. Enabling VoLTE (Voice over LTE) prevents your 4G/5G mobile data from disconnecting during the test call, ensuring fast and seamless verification.

How to Use openNetworkSettings():

Attach flashCallManager.openNetworkSettings() to a "Network Settings" or "Enable VoLTE" button in your Activity:

binding.networkSettingsButton.setOnClickListener {
    flashCallManager.openNetworkSettings()
}

Or call it directly via KasookooSdkHelper:

KasookooSdkHelper.openNetworkSettings(context)

FlashCallActivity.kt

package com.your.package

import android.os.Bundle
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import com.app.sdk.flashcall.*
import com.app.sdk.databinding.ActivityFlashCallBinding // Note: Uses SDK's ViewBinding

class FlashCallActivity : AppCompatActivity() {

    private lateinit var binding: ActivityFlashCallBinding
    private lateinit var flashCallManager: FlashCallManager
    
    /**
     * REQUIRED: Replace with your actual credentials.
     * Throws an error if left empty.
     */
    private val PUBLISHABLE_KEY = "your_key_here" // Your unique key from the Kasookoo dashboard

    private val viewModel: FlashCallViewModel by viewModels {
        FlashCallViewModelFactory(FlashCallRepository(this))
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // Ensure credentials are provided
        if (PUBLISHABLE_KEY == "your_key_here" || PUBLISHABLE_KEY.isBlank()) {
            throw RuntimeException("SDK Error: Please provide a valid PUBLISHABLE_KEY in FlashCallActivity")
        }

        binding = ActivityFlashCallBinding.inflate(layoutInflater)
        setContentView(binding.root)
        
        // 1. Initialize the Manager
        flashCallManager = FlashCallManager(this, viewModel)
        
        // 2. Observe state changes
        observeUiState()
        
        // 3. Setup click listeners
        setupClickListeners()
        
        // 4. Start permission checks and initialization
        flashCallManager.initialize()
    }

    private fun observeUiState() {
        viewModel.uiState.observe(this) { state ->
            updateUi(state)
        }
    }

    private fun updateUi(state: FlashCallUiState) {
        binding.statusTextView.text = state.statusText
        binding.footerTerms.text = state.logs
        
        // Manage layout transitions based on SDK state
        if (state.isVerificationStarted) {
            binding.phoneInputLayout.visibility = View.GONE
            binding.phoneLabel.visibility = View.GONE
            binding.verifyButton.visibility = View.GONE
            binding.titleTextView.visibility = View.GONE
            binding.subtitleTextView.visibility = View.GONE
            binding.statusLayout.visibility = View.VISIBLE
        }

        if (state.isSuccess) {
            binding.statusLayout.visibility = View.GONE
            binding.successLayout.visibility = View.VISIBLE
            
            // Access session details
            val sessionId = state.sessionId
            val msisdn = state.msisdn
            if (sessionId != null && msisdn != null) {
                Log.e( "Session_id","SID: $sessionId, msisdn: $msisdn")
            }
        }

        if (state.isTimeout) {
            resetUi()
            Toast.makeText(this, state.errorMessage ?: "Verification timed out", Toast.LENGTH_LONG).show()
        }

        state.errorMessage?.let {
            if (!state.isTimeout) {
                Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
            }
        }
    }

    private fun resetUi() {
        binding.phoneInputLayout.visibility = View.VISIBLE
        binding.phoneLabel.visibility = View.VISIBLE
        binding.verifyButton.visibility = View.VISIBLE
        binding.titleTextView.visibility = View.VISIBLE
        binding.subtitleTextView.visibility = View.VISIBLE
        binding.statusLayout.visibility = View.GONE
        binding.successLayout.visibility = View.GONE
        viewModel.resetState()
    }

    private fun setupClickListeners() {
        binding.verifyButton.setOnClickListener {
            val phone = binding.etPhone.text.toString().trim()
            if (phone.isEmpty()) {
                binding.etPhone.error = "Phone number required"
                return@setOnClickListener
            }
            
            hideKeyboard()
            viewModel.onVerifyClicked(
                phone = phone,
                key = PUBLISHABLE_KEY
            )
        }

        binding.showLogs.setOnClickListener {
            val isVisible = binding.footerTerms.isVisible
            binding.footerTerms.visibility = if (isVisible) View.GONE else View.VISIBLE
            binding.showLogs.text = if (isVisible) "Show Logs" else "Hide Logs"
        }

        // Open System Network Settings to allow user to enable VoLTE / Mobile Data
        binding.networkSettingsButton.setOnClickListener {
            flashCallManager.openNetworkSettings()
        }
    }

    private fun hideKeyboard() {
        val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
        imm.hideSoftInputFromWindow(currentFocus?.windowToken, 0)
    }

    // Required: Handle permission results for the SDK
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (!flashCallManager.handlePermResult(requestCode, grantResults)) {
            Toast.makeText(this, "Permissions required for flash verification", Toast.LENGTH_LONG).show()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        // Required: Cleanup listeners
        flashCallManager.destroy()
    }
}

Step 6: SDK Lifecycle Summary

To properly run the SDK, ensure you:

  1. Call flashCallManager.initialize(): This checks for runtime permissions and registers the telephony listeners.
  2. Override onRequestPermissionsResult: Delegate the result to flashCallManager.handlePermResult.
  3. Call flashCallManager.destroy(): Always call this in onDestroy() to avoid memory leaks and prevent background listeners from running.
  4. Observe viewModel.uiState: This is how you receive feedback about the verification progress (Loading, Success, Timeout, or Error).
    • On Success: When state.isSuccess is true, you can also retrieve state.sessionId and state.msisdn for your backend records.
    • On Timeout: When state.isTimeout is true, the SDK has reached the maximum wait time for a call. You should call viewModel.resetState() (as shown in resetUi) to allow the user to try again.

Step 7: Handling Flow Failures & Error Scenarios

When integrating the Flash Call SDK, various failure scenarios may occur during the verification lifecycle. Below is a guide on what happens during each failure and the recommended action to take in your app:

1. Missing or Denied Telephony Permissions

  • What Happens: flashCallManager.handlePermResult() returns false, preventing the SDK from listening to incoming call states.
  • What to Do: Prompt the user explaining why READ_PHONE_STATE, READ_CALL_LOG, and READ_PHONE_NUMBERS permissions are required. If permanently denied, direct the user to App Settings to grant permissions manually.

2. Session Initialization Failure (Invalid Key or Network Error)

  • What Happens: KasookooSdkHelper.startFlashCallFeature triggers onError. The state.errorMessage is updated in viewModel.uiState.
  • What to Do:
    • Check that a valid PUBLISHABLE_KEY is configured in your Activity/ViewModel.
    • Verify device internet connectivity.
    • Call viewModel.resetState() and allow the user to try again.

3. Outbound Trigger Failure

  • What Happens: KasookooSdkHelper.triggerFlashCall fails (e.g., invalid phone number format, carrier rejection).
  • What to Do:
    • Ensure the user enters a valid phone number with proper country code format.
    • Display the error message from state.errorMessage.
    • Allow the user to edit their phone number and retry.

4. Call Detection Timeout (state.isTimeout == true)

  • What Happens: The SDK waits for an incoming flash call up to the configured timeout period (default 60 seconds). If no matching call is detected, state.isTimeout becomes true.
  • What to Do:
    • Invoke resetUi() and viewModel.resetState() to return the UI to its initial input state.
    • Show a user-friendly message ("Verification timed out").
    • Allow the user to retry or offer an alternative fallback method (e.g., SMS OTP).

5. Verification API Failure

  • What Happens: KasookooSdkHelper.verifyFlashCall fails during the final verification call.
  • What to Do: The SDK automatically attempts to refresh expired tokens. If verification fails repeatedly, call viewModel.resetState() and prompt the user to start a fresh attempt.

6. Resetting the State

In any failure scenario where you want to allow the user to start over, always invoke:

viewModel.resetState()

This cancels active timeout jobs, resets session data, clears target numbers, and prepares the ViewModel for a new verification attempt.


Step 8: OpenTelemetry & Grafana Telemetry Tracking

The SDK features built-in structured logging and tracing powered by OpenTelemetry (OTel). All key lifecycle events, API request/response payloads, and call detection states are exported asynchronously to Grafana (Loki / Tempo).

1. How Telemetry is Implemented in the SDK

  • Initialization: KasookooSdkHelper.initialize(context, config) sets up the OpenTelemetrySdk with an OTLP HTTP Span Exporter (/v1/traces) and an OTLP HTTP Log Exporter (/v1/logs).
  • Endpoints:
    • Debug/Test Environment (isDebug = true): https://monitoring-test.kasookoo.ai
    • Production Environment (isDebug = false): https://monitoring.kasookoo.ai
  • Correlation Header & call_trace_id:
    • Every API request automatically includes X-Call-Trace-ID and X-Request-ID headers via OkHttp interceptor (TelemetryInterceptor).
    • When the outbound call is triggered (/api/v1/silent-auth-call/sessions/flashcall/start), the returned session_id is automatically set as the call_trace_id.
    • All subsequent telemetry logs and API requests carry this exact call_trace_id for end-to-end trace correlation in Grafana.

2. Log Events Pipeline

Below are the standard structured log events emitted during a Flash Call lifecycle:

Event NameEmitted WhenKey Payload Attributes
SDK_INITIALIZEDSDK is initialized via KasookooSdkHelper.initialize()user_id, service.name
FLASH_CALL_SESSION_INITIATEDstartFlashCallFeature() is invokeduser_id, feature_type
FLASH_CALL_SESSION_CREATEDClient session API succeedssession_id, api_url, api_response (token redacted)
FLASH_CALL_TRIGGER_INITIATEDtriggerFlashCall() is invokeduser_id, feature_type
FLASH_CALL_TRIGGEREDOutbound trigger API succeedscall_trace_id, api_url, api_response
FLASH_CALL_RECEIVEDPolling detects incoming ringing call (CALL_STATE_RINGING)call_trace_id, call_state
FLASH_CALL_VERIFIEDQuick verify API succeedscall_trace_id, api_url, api_response
FLASH_CALL_FAILED / FLASH_CALL_TRIGGER_FAILEDAny step encounters an errorerror, level: "ERROR"

3. JSON Log Format

All log records exported to Grafana Loki are formatted in structured JSON string bodies:

{
  "event": "FLASH_CALL_TRIGGERED",
  "level": "INFO",
  "call_trace_id": "sess_8f3a92b1",
  "user_id": "user_12345",
  "feature_type": "flash_call",
  "api_url": "https://sdk.kasookoo.ai/api/v1/silent-auth-call/sessions/flashcall/start",
  "api_response": "{\"session_id\":\"sess_8f3a92b1\",\"msisdn\":\"+923001234567\",\"fixed_cli\":\"+18005550199\",\"verification_wait_timeout_seconds\":60}"
}

4. Tracking Logs on the Grafana Web Dashboard

You can track and query your SDK telemetry on Grafana using Loki's Explore view or custom dashboards:

Step 1: Open Grafana Explore View

Navigate to your Grafana web console (e.g. https://monitoring.kasookoo.ai or https://monitoring-test.kasookoo.ai) and select the Loki data source under Explore.

Step 2: Querying Logs with LogQL

Use LogQL queries to filter and parse logs in JSON format:

  • Filter all logs for a specific service:

    {service_name="kasookoo-android"} | json
  • Track a specific verification session using call_trace_id:

    {service_name="kasookoo-android"} | json | call_trace_id = "sess_8f3a92b1"
  • Filter by specific event types:

    {service_name="kasookoo-android"} | json | event = "FLASH_CALL_RECEIVED"
  • Filter errors only:

    {service_name="kasookoo-android"} | json | level = "ERROR"

Step 3: Auto-Extracting Fields

By adding | json to your LogQL query, Grafana Loki automatically parses every JSON field (event, call_trace_id, api_url, api_response, user_id, etc.) into selectable panel fields, allowing you to create dashboards, alert rules, and duration metrics.


Did this page help you?