OTPless SDK accepts the user’s identity (phone number or email), authenticates through multiple channels, and returns a secure token upon success.The merchant app sends this token to its backend, which verifies it with the OTPless Server before proceeding with the user journey.
To get started, you need to install dependencies for kotlinx-serialization, kotlin-cocoapods and Otpless Android & iOS dependencies. You can do this by adding following the below mentioned steps:
In your build.gradle.kts(:composeApp) file, add the following code:
Copy
plugins { alias(libs.plugins.kotlinCocoapods) kotlin("plugin.serialization") version "2.1.10"}
Adding Otpless SDK dependencies and kotlinx-serialization in your build.gradle.kts(:composeApp) file:
Copy
kotlin { sourceSets { androidMain.dependencies { implementation("io.github.otpless-tech:otpless-headless-sdk:0.2.0") } commonMain.dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") } } // Cocoapods configuration: This will create a cocoapod for your iOS app. // So, set the description that you would want in your Podspec file. cocoapods { homepage = "Your homepage name" summary = "Your cocoapods summary" version = "1.0" ios.deploymentTarget = "15.3" podfile = project.file("../iosApp/Podfile") framework { baseName = "composeApp" isStatic = true } // Add the Otpless SDK dependency to the iOS framework pod("OtplessBM/Core") { version = "1.1.3" extraOpts += listOf("-compiler-option", "-fmodules") } }}
Replace YOUR_APP_ID with your actual App
ID provided in
your OTPLESS dashboard.
Add Network Security Config inside your android/app/src/main/AndroidManifest.xml file into your <application> code block (Only required if you are using the SNA feature):
Step 3: Create expect function declarations in commonMain
In your commonMain source set, create a new file named OtplessAuthHandler.kt and add the following code:
Copy
expect fun initializeOtpless(appId: String, onOtplessResponse: (String) -> Unit, loginUri: String?)expect fun start(otplessCMPRequest: OtplessCMPRequest)expect fun cleanup()data class OtplessCMPRequest( val phoneNumber: String? = null, val countryCode: String? = null, val email: String? = null, val otp: String? = null, val otpExpiry: String? = null, val otpLength: String? = null, val deliveryChannel: String? = null, val oAuthChannel: String? = null)
In your LoginScreen.kt file, initialize the Otpless SDK. Make sure to initialize the SDK in DisposableEffect block to free the resources consumed by Otpless once your LoginScreen.kt is destroyed.
Copy
DisposableEffect(key1 = Unit) { // Use key1 as Unit so that the SDK is not re-initialized on recomposition initializeOtpless( appId = "YOUR_APPID", onOtplessResponse = { otplessResponseString -> response = otplessResponseString + "\n\n" + response handleOtplessResponse(otplessResponseString, onOTPAutoRead = { otp = it }) }, loginUri = null ) onDispose { cleanup() }}
To handle the Otpless response, create a function named handleOtplessResponse in your LoginScreen.kt file. This function will handle the response received from the Otpless SDK.
Copy
import kotlinx.serialization.json.Jsonimport kotlinx.serialization.json.JsonObjectimport kotlinx.serialization.json.contentOrNullimport kotlinx.serialization.json.intOrNullimport kotlinx.serialization.json.jsonObjectimport kotlinx.serialization.json.jsonPrimitivefun handleOtplessResponse(responseJsonString: String, onOTPAutoRead: (String) -> Unit) { val parsedElement = Json.parseToJsonElement(responseJsonString).jsonObject val responseType = parsedElement["responseType"]?.jsonPrimitive?.contentOrNull val statusCode = parsedElement["statusCode"]?.jsonPrimitive?.intOrNull val responseJsonObject = parsedElement["response"]?.jsonObject when (responseType) { "SDK_READY" -> { // SDK initialized successfully, enable continue button log("SDK is ready!") } "FAILED" -> { log("SDK initialization failed!") if (statusCode == 5003) { // SDK initialization failed, retry initializing } else { // General failure handling } } "INITIATE" -> { if (statusCode != 200) { if (getPlatformName().lowercase() == "android") { handleInitiateErrorAndroid(responseJsonObject) } else { handleInitiateErrorIos(responseJsonObject) } } else { val authType = responseJsonObject?.get("authType")?.jsonPrimitive?.contentOrNull when (authType) { "OTP" -> { log("Authentication started using OTP") // Take user to OTP verification screen } "SILENT_AUTH" -> { log("Authentication started using Silent Auth") // Handle Silent Authentication initiation (show loading) } } } } "OTP_AUTO_READ" -> { // Only applicable in Android val otp = responseJsonObject?.get("otp")?.jsonPrimitive?.contentOrNull if (!otp.isNullOrBlank()) { // Autofill OTP in your text field onOTPAutoRead(otp) } } "VERIFY" -> { val authType = responseJsonObject?.get("authType")?.jsonPrimitive?.contentOrNull if (authType == "SILENT_AUTH") { if (statusCode == 9106) { // Silent Auth + fallback failed → gracefully exit auth flow log("SNA + fallback failed!") } else { log("SNA failed, trying fallback!") } } else { if (getPlatformName().lowercase() == "android") { handleVerifyErrorAndroid(responseJsonObject) } else { handleVerifyErrorIos(responseJsonObject) } } } "DELIVERY_STATUS" -> { val authType = responseJsonObject?.get("authType")?.jsonPrimitive?.contentOrNull val deliveryChannel = responseJsonObject?.get("deliveryChannel")?.jsonPrimitive?.contentOrNull // Handle delivery status (authType, deliveryChannel) log("DELIVERY_STATUS: authType: $authType \b deliveryChannel: $deliveryChannel") } "FALLBACK_TRIGGERED" -> { val newDeliveryChannel = responseJsonObject?.get("deliveryChannel")?.jsonPrimitive?.contentOrNull // Handle fallback deliveryChannel log("Fallback triggered, new delivery channel: $newDeliveryChannel") } "ONETAP" -> { val data = responseJsonObject?.get("data")?.jsonObject val token = data?.get("token")?.jsonPrimitive?.contentOrNull if (!token.isNullOrBlank()) { // Process token and proceed log("Token: $token") } } }}
To verify the OTP entered by the user, use the verify method with the necessary parameters. Verifying OTP is required only in case of OTP authentication. No need to verify OTP in case of MAGICLINK.