otpless_headless_flutter 3.0.1
otpless_headless_flutter: ^3.0.1 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 2.0.1, OtplessBM/Core 3.0.1 as of plugin 3.0.1) 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 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 3.0.1on the trunk. If you useOtplessChannelType.GOOGLE_SDKorFACEBOOK_SDK, add the matching subspec (OtplessBM/GoogleSupport,OtplessBM/FacebookSupport) to yourios/Podfile.
| Plugin | Android otpless-headless-sdk |
iOS OtplessBM/Core |
|---|---|---|
| 3.0.1 | 2.0.1 | 3.0.1 |
| 3.0.0 | 2.0.1 | 3.0.0 |
| 2.0.0 | 0.9.0 | 2.3.2 |
Platform support matrix #
| Dart method | Android | iOS |
|---|---|---|
initialize (sslPinning, loginUri — new in 2.1) |
✅ | ✅ |
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);
}
initialize options (3.0.0+) #
Future<void> initialize(
String appId, {
OtplessSslPinning sslPinning = OtplessSslPinning.disabled,
String? loginUri,
});
| Parameter | Default | Notes |
|---|---|---|
sslPinning |
OtplessSslPinning.disabled |
Opt-in SSL certificate pinning of the OTPLESS backend on both platforms. See SSL pinning. |
loginUri |
null (SDK derives otpless.<appid>://otpless) |
Deep-link URI the SDK returns to after OAuth channels. Forwarded to both native SDKs. |
SSL pinning #
Pinning is off by default. Turn it on at initialisation time:
_otplessHeadlessPlugin.initialize(
"YOUR_APP_ID",
sslPinning: OtplessSslPinning.enabled,
);
With pinning enabled the native SDKs (Android otpless-headless-sdk 2.0.1, iOS OtplessBM 3.0.1) validate the certificate chain of the OTPLESS backend (sigma.otpless.app) against a signed remote pin manifest before any authentication request is sent. If validation fails the SDK fails closed: no request leaves the device and your response callback receives
{
"responseType": "FAILED",
"statusCode": 5004,
"response": { "errorCode": "5004", "errorMessage": "SSL pin validation failed" }
}
Common causes are a debugging proxy (Charles, Proxyman, mitmproxy) or a corporate TLS-inspecting gateway. Do not enable pinning in builds you intend to inspect with a proxy. Pinning cannot be toggled without calling initialize again.
Google Play Integrity (Android) needs no plugin configuration: it is internal to otpless-headless-sdk 2.0.1 and activates with the dependency bump. Your app must be distributed through Google Play and linked to a Play Console project for attestation to succeed; iOS has no equivalent.
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":
// Terminal SDK-level failure. See "SDK-level FAILED codes" below.
final code = result["statusCode"];
if (code == 5004) {
debugPrint("SSL pin validation failed; nothing was sent to the backend");
} else {
debugPrint("SDK initialization failed: ${result["response"]}");
}
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;
}
}
SDK-level FAILED codes #
These arrive with responseType: "FAILED"; statusCode and response.errorCode carry the same value.
statusCode |
errorMessage |
Meaning | Platforms |
|---|---|---|---|
5003 |
Failed to initialize the SDK |
initialize could not complete (network, bad appId, etc.). Retry initialize. |
Android, iOS |
5004 |
SSL pin validation failed |
sslPinning was enabled and the backend certificate did not match the pinned set. The SDK fails closed; no auth request was sent. Check for a MITM proxy, or initialise with OtplessSslPinning.disabled. |
Android, iOS (2.1.0+) |
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 2.0 to 3.0 #
One breaking change: initialize(..., timeout:) is removed (it was never applied natively) — drop timeout: from your call site. initialize gains two optional named parameters (sslPinning, loginUri). Your response handler may now receive statusCode: 5004 when you opt in to pinning (see SSL pinning).
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