otpless_headless_flutter 2.0.0
otpless_headless_flutter: ^2.0.0 copied to clipboard
Standalone SDK for Otpless Headless functionality.
OTPLESS Flutter Headless SDK #
The new Headless Authentication SDK offers faster performance, greater reliability, and enhanced security. For a smoother authentication and integration experience, we strongly recommend migrating by removing the old SDK and following the steps below.
Install OTPLESS SDK Dependency #
Installation #
dependencies: otpless_headless_flutter: ^<latest_version>
flutter pub get
Toolchain requirements (2.0.0+) #
The underlying native SDKs (otpless-headless-sdk 0.9.0, OtplessBM/Core 2.3.2) pull in transitive dependencies that require newer toolchains than the pre-2.0 releases needed:
- Android: Android Gradle Plugin 8.9.1+ and
compileSdkVersion36+. The 0.9.0 Android SDK transitively depends onandroidx.core:core:1.18.0, which enforces this minimum. Updateandroid/settings.gradleandandroid/app/build.gradlein your consuming app accordingly. - iOS: deployment target 13.0+ (unchanged). CocoaPods with
OtplessBM/Core 2.3.2on the trunk. If you useOtplessChannelType.GOOGLE_SDKorFACEBOOK_SDK, add the matching subspec (OtplessBM/GoogleSupport,OtplessBM/FacebookSupport) to yourios/Podfile.
Platform support matrix #
| Dart method | Android | iOS |
|---|---|---|
initialize |
✅ | ✅ |
setResponseCallback |
✅ | ✅ |
setDevLogging |
✅ | ✅ |
start |
✅ | ✅ |
startOnetap (OneTap discovery) — renamed from startBackground |
✅ | ✅ (new in 2.0) |
commitResponse |
✅ | ✅ |
isSdkReady |
✅ | ✅ |
sendUserAuthEvent |
✅ | ✅ (new in 2.0) |
setMfaEnabled |
✅ | ✅ |
initSession / getActiveSession / logoutSession |
✅ | ✅ |
startInBackground (silent regular auth) |
✅ | ❌ (no SDK equivalent) |
isWhatsAppInstalledForAndroid |
✅ | ❌ returns false |
initTrueCaller |
✅ | ❌ returns false (no iOS Truecaller SDK) |
checkSimBindingStatus / clearSimBinding / setSimBindingEnabled |
✅ | ❌ (no SDK equivalent) |
closeDialogIfOpen |
✅ | ❌ (no SDK equivalent) |
| Deep-link handling / Passkey / Facebook SDK register | via manifest / no-op | client wires up in AppDelegate / SceneDelegate (see below) |
Initialize the SDK #
Import #
import 'package:otpless_headless_flutter/otpless_flutter.dart';
final _otplessHeadlessPlugin = Otpless();
@override
void initState() {
super.initState();
_otplessHeadlessPlugin.initialize("YOUR_APP_ID");
_otplessHeadlessPlugin.setResponseCallback(onOtplessResponse);
}
Initiate Authentication #
Phone Auth #
Request #
void startWithPhone(String phoneNumber) {
final Map<String, dynamic> args = {
"phone": "phoneNumber",
"countryCode": "countryCode",
};
_otplessHeadlessPlugin.start(onOtplessResponse, args);
}
Verify #
void verifyPhoneOtp(String phoneNumber, String otp) {
final Map<String, dynamic> args = {
"phone": "phoneNumber",
"countryCode": "countryCode",
"otp": "otp",
};
_otplessHeadlessPlugin.start(onOtplessResponse, args);
}
Response Handling #
void onOtplessResponse(dynamic result) {
_otplessHeadlessPlugin.commitResponse(result);
final responseType = result['responseType'];
switch (responseType) {
case "SDK_READY":
debugPrint("SDK is ready");
break;
case "FAILED":
debugPrint("SDK initialization failed");
// Handle SDK initialization failure
break;
case "INITIATE":
if (result["statusCode"] == 200) {
debugPrint("Headless authentication initiated");
final authType = result["response"]["authType"]; // This is the authentication type
if (authType == "OTP") {
// Take user to OTP verification screen
} else if (authType == "SILENT_AUTH") {
// Handle Silent Authentication initiation by showing
// loading status for SNA flow.
}
} else {
// Handle initiation error.
// To handle initiation error response, please refer to the error handling section.
if (Platform.isAndroid) {
handleInitiateErrorAndroid(result["response"]);
} else if (Platform.isIOS) {
handleInitiateErrorIOS(result["response"]);
}
}
break;
case "OTP_AUTO_READ":
// OTP_AUTO_READ is triggered only in ANDROID devices for WhatsApp and SMS.
final otp = result["response"]["otp"];
debugPrint("OTP Received: $otp");
break;
case "VERIFY":
final authType = result["response"]["authType"];
if (authType == "SILENT_AUTH") {
if (result["statusCode"] == 9106) {
// Silent Authentication and all fallback authentication methods in SmartAuth have failed.
// The transaction cannot proceed further.
// Handle the scenario to gracefully exit the authentication flow
} else {
// Silent Authentication failed.
// If SmartAuth is enabled, the INITIATE response
// will include the next available authentication method configured in the dashboard.
}
} else {
// To handle verification failed response, please refer to the error handling section.
if (Platform.isAndroid) {
handleVerifyErrorAndroid(result["response"]);
} else if (Platform.isIOS) {
handleVerifyErrorIOS(result["response"]);
}
}
break;
case "DELIVERY_STATUS":
// This function is called when delivery is successful for your authType.
final authType = result["response"]["authType"];
// It is the authentication type (OTP, MAGICLINK, OTP_LINK) for which the delivery status is being sent
final deliveryChannel = result["response"]["deliveryChannel"];
// It is the delivery channel (SMS, WHATSAPP, etc) on which the authType has been delivered
break;
case "ONETAP":
final token = result["response"]["token"];
if (token != null) {
debugPrint("OneTap Data: $token");
// Process token and proceed
}
break;
case "FALLBACK_TRIGGERED":
// A fallback occurs when an OTP delivery attempt on one channel fails,
// and the system automatically retries via the subsequent channel selected on Otpless Dashboard.
// For example, if a merchant opts for SmartAuth with primary channal as WhatsApp and secondary channel as SMS,
// in that case, if OTP delivery on WhatsApp fails, the system will automatically retry via SMS.
// The response will contain the deliveryChannel to which the OTP has been sent.
final newDeliveryChannel = result["response"]["deliveryChannel"];
if (newDeliveryChannel != null) {
// This is the deliveryChannel to which the OTP has been sent
}
break;
default:
debugPrint("Unknown response type: $responseType");
break;
}
}
Android manifest update #
Add Network Security Config inside your android/app/src/main/AndroidManifest.xml file into your
android:networkSecurityConfig="@xml/otpless_network_security_config"
Ios info.plist update #
Add the following block to your ios/Runner/info.plist file (Only required if you are using the SNA feature):
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>80.in.safr.sekuramobile.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>TLSv1.1</string>
</dict>
<key>partnerapi.jio.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>TLSv1.1</string>
</dict>
</dict>
</dict>
iOS AppDelegate / SceneDelegate integration #
The plugin does not wrap iOS URL handlers, Facebook SDK registration, or WebAuthn. If your merchant flow requires them, add the following in your host iOS app.
Deep-link callback (required for OAuth channel types like GMAIL, TWITTER, etc.) #
In ios/Runner/AppDelegate.swift:
import OtplessBM
override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
if Otpless.shared.isOtplessDeeplink(url: url) {
Task { await Otpless.shared.handleDeeplink(url) }
return true
}
return super.application(app, open: url, options: options)
}
For SceneDelegate apps, add to SceneDelegate.swift:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
for context in URLContexts where Otpless.shared.isOtplessDeeplink(url: context.url) {
Task { await Otpless.shared.handleDeeplink(context.url) }
}
}
Facebook SDK (only if using FACEBOOK_SDK channel) #
Add OtplessBM/FacebookSupport subspec to ios/Podfile. Then wire it up in AppDelegate.swift:
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Otpless.shared.registerFBApp(application, didFinishLaunchingWithOptions: launchOptions)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
Otpless.shared.registerFBApp(app, open: url, options: options)
return super.application(app, open: url, options: options)
}
Passkey / WebAuthn #
Otpless.shared.authorizeViaPasskey(withRequest:windowScene:) is not exposed through this Flutter plugin. If your flow needs it, call it from Swift with a resolved UIWindowScene.
Migration from 1.x to 2.0 #
Breaking changes #
startBackground(callback, config)is renamed tostartOnetap(callback, config). The old name implied a background OTP flow, but the method actually presents the OneTap / verified-contact sheet. Replace every call site.- iOS parity for
startOnetapandsendUserAuthEvent. These now execute on both platforms instead of silently returningfalseon iOS. If your app relied on iOS being a no-op, add aPlatform.isAndroidguard on your side. - New
responseTypevalues may arrive in the callback:AUTH_TERMINATED,MFA_FACTOR_COMPLETED,AUTO_FLOW_ACTION(Android only). Callers that switch exhaustively onresponseTypemust add cases. OtplessAuthConfigconstructor gains an optionaldeviceFingerprintModeparameter (defaults toDeviceFingerprintMode.none). Backwards compatible for positional callers.
New features #
setMfaEnabled(bool)— enable MFA. Watch forMFA_FACTOR_COMPLETEDin your response handler.initSession(appId)/getActiveSession()/logoutSession()— JWT-based session persistence.startInBackground(callback, requestMap)— Android-only silent variant ofstart()that suppressesOTP_AUTO_READintermediates.- Android-only:
checkSimBindingStatus(),clearSimBinding(),setSimBindingEnabled(bool),closeDialogIfOpen(). - Extra keys accepted on the
start()/startInBackground()request map:tid,code,extras(Map<String, String>),requestId,deviceFingerprintMode.
Bug fix #
sendUserAuthEvent(...): theproviderInfooptional parameter was previously dropped due to an inverted null check on the plugin side. Fixed in 2.0.0; downstream analytics receive it now.
Note #
For complete documentation and other login feature explore, follow the following guide here: installation guide here