How to Send OTP in Flutter with Firebase Backend (2026)
Flutter OTP tutorial using StartMessaging on a Firebase Functions backend. Riverpod state, http client, secure cookie session via Firebase Auth custom token.
StartMessaging Team
Engineering
Flutter apps in India typically front a Firebase backend. This tutorial uses Firebase Functions (with our backend guide) and a Flutter front-end.
Overview
- Backend: Firebase Functions calling StartMessaging.
- Flutter:
cloud_functionsclient to invoke callables. - On verify, sign in with Firebase Auth custom token.
- SMS Retriever auto-fill on Android.
Flutter Setup
flutter create otp_flutter
cd otp_flutter
flutter pub add firebase_core firebase_auth cloud_functions flutter_riverpod sms_autofillOtpService Class
// lib/services/otp_service.dart
import 'package:cloud_functions/cloud_functions.dart';
import 'package:firebase_auth/firebase_auth.dart';
class OtpService {
final _fns = FirebaseFunctions.instance;
Future<({String requestId, DateTime expiresAt})> send(String phone) async {
final r = await _fns.httpsCallable('sendOtp').call({'phoneNumber': phone});
return (
requestId: r.data['requestId'] as String,
expiresAt: DateTime.parse(r.data['expiresAt'] as String),
);
}
Future<void> verifyAndSignIn(String requestId, String code, String phone) async {
final r = await _fns.httpsCallable('verifyOtp').call({
'requestId': requestId,
'otpCode': code,
'phoneNumber': phone,
});
final token = r.data['token'] as String;
await FirebaseAuth.instance.signInWithCustomToken(token);
}
}Login UI with Riverpod
final otpServiceProvider = Provider((ref) => OtpService());
class LoginScreen extends ConsumerStatefulWidget {
@override
ConsumerState<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends ConsumerState<LoginScreen> {
String? requestId;
final phoneCtl = TextEditingController();
final codeCtl = TextEditingController();
Future<void> _send() async {
final r = await ref.read(otpServiceProvider).send(phoneCtl.text);
setState(() => requestId = r.requestId);
}
Future<void> _verify() async {
await ref.read(otpServiceProvider).verifyAndSignIn(requestId!, codeCtl.text, phoneCtl.text);
}
@override
Widget build(BuildContext context) {
return requestId == null
? Column(children: [TextField(controller: phoneCtl), ElevatedButton(onPressed: _send, child: Text('Send OTP'))])
: Column(children: [TextField(controller: codeCtl), ElevatedButton(onPressed: _verify, child: Text('Verify'))]);
}
}OTP Auto-Fill
Use the sms_autofill package + Android SMS Retriever API. See our auto-fill guide.
FAQ
React Native equivalent in our React Native guide.
Related Articles
React Native OTP tutorial using Expo and StartMessaging. Includes secure storage, auto-fill (Android SMS Retriever / iOS keychain), and a Node backend pattern.
Firebase Functions OTP tutorial using StartMessaging. Callable functions, Firestore for rate-limit, Firebase Auth custom-token issuance after OTP verification.
Keep TRAI DLT-compliant SMS OTP on the server: React Native and Flutter clients call your API only—never ship SMS gateway keys for OTP SMS API India integrations.
Ready to Send OTPs?
Integrate StartMessaging in 5 minutes. No DLT registration required.