Flutter OTP Kit
π― Why Choose Flutter OTP Kit?
The most comprehensive, feature-rich, and performant OTP verification solution for Flutter. Built with modern architecture, enterprise-grade security, and unlimited customization options.
β¨ Key Features
- π Complete Solution - Timer, validation, masking, SMS autofill, biometric integration
- π¨ Unlimited Customization - 16+ cursor styles, 7+ field shapes, 10+ animation types
- π Enterprise Security - Rate limiting, biometric integration, audit logging
- β‘ Performance First - Lazy loading, memory optimization, animation pooling
- π± Platform Optimized - iOS, Android, Web, Desktop support
- βΏ Accessibility Ready - Screen reader support, keyboard navigation, voice control
π± Demo
Basic Theme |
Rotate Animation |
Rounded Fields |
 |
 |
 |
Scale Animation |
Underline Style |
Cursor Styles |
 |
 |
 |
π Quick Start
Installation
Add this to your pubspec.yaml
:
dependencies:
flutter_otp_kit: ^3.0.3
Then run:
flutter pub get
Basic Usage
import 'package:flutter_otp_kit/flutter_otp_kit.dart';
class OtpScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: OtpKit(
title: 'Verify Phone Number',
subtitle: 'Enter the code sent to +1 (555) 123-4567',
fieldCount: 4,
onVerify: (otp) async {
// Verify OTP with your backend
return await verifyOtpWithBackend(otp);
},
onResend: () {
// Resend OTP logic
resendOtp();
},
),
);
}
}
π¨ Customization Examples
Modern Design with Animations
OtpKit(
title: 'Verify Email',
subtitle: 'Enter the verification code',
fieldCount: 6,
// Modern field styling
fieldConfig: OtpFieldConfig.preset(OtpFieldPreset.modern),
// Smooth animations
animationConfig: OtpAnimationConfig(
enableAnimation: true,
fieldFillAnimationType: FieldFillAnimationType.rotate,
errorFieldAnimationType: ErrorFieldAnimationType.bounce,
),
// Colors and theming
primaryColor: Colors.purple,
successColor: Colors.green,
errorColor: Colors.red,
onVerify: (otp) async => await verifyOtp(otp),
onResend: () => resendOtp(),
)
Rounded Fields with Custom Styling
OtpKit(
title: 'Security Code',
fieldCount: 4,
// Custom field configuration
fieldConfig: OtpFieldConfig(
fieldWidth: 60,
fieldHeight: 60,
borderRadius: 30, // Circular fields
borderWidth: 2,
fieldShape: OtpFieldShape.circle,
// Shadow effects
enableShadow: true,
shadowColor: Colors.blue.withOpacity(0.3),
shadowBlurRadius: 8,
// Typography
fieldFontSize: 24,
fieldFontWeight: FontWeight.bold,
),
onVerify: (otp) async => await verifyOtp(otp),
onResend: () => resendOtp(),
)
Underlined Style (Minimal Design)
OtpKit(
title: 'Enter Code',
fieldCount: 4,
// Underlined field preset
fieldConfig: OtpFieldConfig.preset(OtpFieldPreset.underlined),
// Slide animation
animationConfig: OtpAnimationConfig(
enableAnimation: true,
fieldFillAnimationType: FieldFillAnimationType.slide,
),
// Minimal colors
primaryColor: Colors.black,
backgroundColor: Colors.transparent,
onVerify: (otp) async => await verifyOtp(otp),
onResend: () => resendOtp(),
)
π§ Advanced Configuration
Enterprise Features
OtpKit(
title: 'Secure Verification',
fieldCount: 6,
// SMS Autofill
smsConfig: OtpSmsConfig(
enableSmsAutofill: true,
enableSmartAuth: true,
enableSmsRetrieverAPI: true, // Android
enableSmsUserConsentAPI: true, // Android
),
// Security features
securityConfig: OtpSecurityConfig(
enableRateLimiting: true,
maxAttemptsPerMinute: 3,
maxAttemptsPerHour: 15,
lockoutDuration: Duration(minutes: 10),
enableBiometricIntegration: true,
enableAuditLogging: true,
),
// Performance optimization
performanceConfig: OtpPerformanceConfig(
enableLazyLoading: true,
enableMemoryOptimization: true,
enableAnimationPooling: true,
enablePerformanceMonitoring: true,
),
// Error handling
errorConfig: OtpErrorConfig(
maxErrorRetries: 3,
enableFieldLockout: true,
clearFieldsOnError: true,
enableHapticFeedbackOnError: true,
),
onVerify: (otp) async => await verifyOtp(otp),
onResend: () => resendOtp(),
)
Custom Cursor Styles
// Different cursor styles available
OtpKit(
fieldConfig: OtpFieldConfig(
cursorStyle: CursorStyle.bar, // Vertical bar
// cursorStyle: CursorStyle.block, // Filled block
// cursorStyle: CursorStyle.underline, // Underline
// cursorStyle: CursorStyle.outline, // Rectangle outline
// cursorStyle: CursorStyle.doubleBar, // Two bars
// cursorStyle: CursorStyle.none, // No cursor
cursorColor: Colors.blue,
cursorWidth: 2.0,
),
// Cursor animation
animationConfig: OtpAnimationConfig(
enableCursorAnimation: true,
cursorBlinkDuration: Duration(milliseconds: 800),
),
onVerify: (otp) async => await verifyOtp(otp),
onResend: () => resendOtp(),
)
π Field Shapes & Presets
Available Field Shapes
enum OtpFieldShape {
rectangle, // Square corners
roundedRectangle, // Rounded corners
circle, // Circular shape
stadium, // Pill shape (completely rounded)
underlined, // Bottom border only
outlined, // Material design outlined
custom, // Custom shape
}
Quick Presets
// Use predefined presets for quick setup
OtpFieldConfig.preset(OtpFieldPreset.basic) // Simple rectangle
OtpFieldConfig.preset(OtpFieldPreset.modern) // Modern rounded
OtpFieldConfig.preset(OtpFieldPreset.rounded) // Circular fields
OtpFieldConfig.preset(OtpFieldPreset.underlined) // Minimal underlined
π¬ Animation Types
Field Fill Animations
enum FieldFillAnimationType {
none, // No animation
scale, // Scale up when filled
rotate, // Rotate when filled
slideLeft, // Slide from left
slideRight, // Slide from right
slideUp, // Slide from top
slideDown, // Slide from bottom
autoSlide, // Auto-detect direction based on text direction
}
Error Animations
enum ErrorFieldAnimationType {
none, // No error animation
shake, // Horizontal shake
bounce, // Bounce effect
pulse, // Pulse/scale effect
wiggle, // Wiggle animation
}
Animation Configuration Example
OtpAnimationConfig(
// Enable animations
enableAnimation: true,
enableFieldStateAnimation: true,
enableCursorAnimation: true,
// Animation timing
animationDuration: Duration(milliseconds: 300),
animationCurve: Curves.easeOutCubic,
// Field fill animation
fieldFillAnimationType: FieldFillAnimationType.scale,
fieldFillScaleFactor: 1.1,
// Error animation
errorFieldAnimationType: ErrorFieldAnimationType.shake,
errorShakeAmplitude: 8.0,
errorShakeFrequency: 15.0,
// Cursor animation
cursorBlinkDuration: Duration(milliseconds: 800),
)
π Security Features
Rate Limiting
OtpSecurityConfig(
enableRateLimiting: true,
maxAttemptsPerMinute: 3,
maxAttemptsPerHour: 15,
lockoutDuration: Duration(minutes: 10),
)
Biometric Integration
OtpSecurityConfig(
enableBiometricIntegration: true,
biometricTimeout: Duration(seconds: 30),
)
// Check biometric availability
final isAvailable = await OtpBiometricService.instance.isBiometricAvailable();
if (isAvailable) {
final authenticated = await OtpBiometricService.instance.authenticate();
}
Advanced Validation
OtpSecurityConfig(
enableAdvancedValidation: true,
validationPattern: r'^\d{6}$', // 6-digit numbers only
enableEncryption: true,
enableAuditLogging: true,
)
π± SMS Autofill
iOS Native SMS
OtpSmsConfig(
enableSmsAutofill: true,
// iOS automatically detects SMS codes
)
Android SMS Retriever API
OtpSmsConfig(
enableSmsRetrieverAPI: true,
appSignature: 'YOUR_APP_SIGNATURE', // Required for SMS Retriever
)
Android User Consent API
OtpSmsConfig(
enableSmsUserConsentAPI: true,
senderPhoneNumber: '+1234567890', // Optional: filter by sender
)
Smart Auth Integration
OtpSmsConfig(
enableSmartAuth: true,
enableSmsValidation: true,
smsValidationRegex: r'\b\d{6}\b', // Extract 6-digit codes
smsTimeout: Duration(minutes: 5),
)
Memory Management
OtpPerformanceConfig(
enableMemoryOptimization: true,
enableMemoryLeakDetection: true,
enableBackgroundCleanup: true,
cleanupInterval: Duration(minutes: 2),
)
Animation Optimization
OtpPerformanceConfig(
enableAnimationOptimization: true,
enableAnimationPooling: true,
maxAnimationPoolSize: 12,
animationCleanupDelay: Duration(seconds: 3),
)
Lazy Loading
OtpPerformanceConfig(
enableLazyLoading: true,
maxVisibleFields: 8, // Only render visible fields
enableFieldRecycling: true,
)
π― Error Handling
Error Configuration
OtpErrorConfig(
// Retry logic
maxErrorRetries: 3,
enableFieldLockout: true,
fieldLockoutDuration: Duration(seconds: 30),
// Error display
errorTextMaxLines: 2,
showErrorIcon: true,
errorIcon: Icons.error_outline,
// Error behavior
clearFieldsOnError: true,
autoClearErrorOnInput: true,
// Haptic feedback
enableHapticFeedbackOnError: true,
errorHapticFeedbackType: ErrorHapticFeedbackType.heavy,
// Error animations
errorShakeEffect: true,
errorShakeDuration: Duration(milliseconds: 500),
errorShakeCount: 3,
)
Error Handling Example
OtpKit(
onVerify: (otp) async {
try {
final isValid = await verifyOtpWithBackend(otp);
if (isValid) {
// Success - navigate to next screen
Navigator.pushReplacement(context, MaterialPageRoute(
builder: (context) => HomeScreen(),
));
return true;
} else {
// Invalid OTP
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Invalid OTP. Please try again.')),
);
return false;
}
} catch (e) {
// Network or other error
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error verifying OTP: $e')),
);
return false;
}
},
// Error state change callback
onErrorStateChanged: (hasError) {
if (hasError) {
print('OTP verification failed');
}
},
)
π Internationalization & Accessibility
RTL Support
OtpKit(
// Automatic RTL detection
textDirection: TextDirection.rtl, // or TextDirection.ltr
// RTL-aware animations
animationConfig: OtpAnimationConfig(
fieldFillAnimationType: FieldFillAnimationType.autoSlide,
),
)
Accessibility
OtpKit(
// Screen reader support
enableScreenReaderSupport: true,
// Semantic labels
title: 'Verify Phone Number',
subtitle: 'Enter the 4-digit code sent to your phone',
// Keyboard navigation
enableInteractiveSelection: true,
unfocusOnTapOutside: true,
)
Custom Text Styling
OtpKit(
// Title styling
titleStyle: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.black,
),
// Subtitle styling
subtitleStyle: TextStyle(
fontSize: 16,
color: Colors.grey[600],
),
// Button styling
buttonStyle: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
)
π Callbacks & Events
Available Callbacks
OtpKit(
// Required callbacks
onVerify: (String otp) async {
// Return true for success, false for failure
return await verifyOtp(otp);
},
onResend: () {
// Resend OTP logic
resendOtp();
},
// Optional callbacks
onChanged: (String otp) {
print('Current OTP: $otp');
},
onCompleted: (String otp) {
print('OTP completed: $otp');
// Auto-verify when completed
},
onTimerChanged: (int remainingSeconds) {
print('Timer: $remainingSeconds seconds remaining');
},
onErrorStateChanged: (bool hasError) {
print('Error state: $hasError');
},
onValidationStateChanged: (bool isValid) {
print('Validation state: $isValid');
},
onCompletionStateChanged: (bool isCompleted) {
print('Completion state: $isCompleted');
},
)
iOS Features
- Face ID Integration - Seamless biometric authentication
- Touch ID Support - Fingerprint authentication
- Native SMS Autofill - Automatic SMS code detection
- VoiceOver Support - Full accessibility support
Android Features
- Fingerprint Authentication - Biometric security
- Face Recognition - Advanced biometric options
- SMS Retriever API - Automatic SMS reading
- SMS User Consent API - User-controlled SMS access
- TalkBack Support - Accessibility features
Web Features
- Keyboard Navigation - Full keyboard support
- Screen Reader Support - Web accessibility
- Responsive Design - Adaptive layouts
- Performance Optimization - Web-specific optimizations
Desktop Features
- Platform Layouts - Native desktop feel
- Keyboard Navigation - Desktop-optimized controls
- Accessibility - Desktop accessibility features
- Performance - Desktop-specific optimizations
Real-time Monitoring
// Enable performance monitoring
OtpPerformanceConfig(
enablePerformanceMonitoring: true,
)
// Access performance metrics
final monitor = OtpPerformanceMonitor.instance;
final metrics = await monitor.getPerformanceMetrics();
print('Memory usage: ${metrics.memoryUsage}MB');
print('Animation FPS: ${metrics.animationFps}');
print('Field render time: ${metrics.fieldRenderTime}ms');
Memory Optimization
// Automatic memory cleanup
OtpPerformanceConfig(
enableMemoryOptimization: true,
enableMemoryLeakDetection: true,
enableBackgroundCleanup: true,
)
// Manual cleanup
OtpPerformanceMonitor.instance.cleanup();
π Migration Guide
From Previous Versions
Old Way (still works but deprecated):
OtpVerificationWidget(
onVerify: (otp) => backend.verify(otp),
onResend: () => backend.resend(),
)
New Way (recommended):
OtpKit(
onVerify: (otp) async => await backend.verify(otp),
onResend: () => backend.resend(),
)
Breaking Changes in 3.0.0
- Animations disabled by default - Explicitly enable with
enableAnimation: true
- New OtpKit widget - Replaces OtpVerificationWidget (still backward compatible)
- Performance-first approach - Zero overhead unless features are enabled
π¨ Troubleshooting
Common Issues
SMS Autofill Not Working
Android:
// Ensure app signature is correct
OtpSmsConfig(
enableSmsRetrieverAPI: true,
appSignature: 'YOUR_CORRECT_APP_SIGNATURE',
)
// Check permissions in AndroidManifest.xml
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="com.google.android.gms.auth.api.phone.permission.SEND" />
iOS:
// Ensure SMS format is correct
// SMS should contain app-associated domain
// Disable animations for better performance
OtpAnimationConfig(
enableAnimation: false,
)
// Enable performance optimizations
OtpPerformanceConfig(
enableMemoryOptimization: true,
enableAnimationOptimization: true,
)
Biometric Issues
// Check biometric availability first
final isAvailable = await OtpBiometricService.instance.isBiometricAvailable();
if (!isAvailable) {
// Handle no biometric support
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Biometric Not Available'),
content: Text('Please set up biometric authentication in device settings.'),
),
);
}
π API Reference
OtpKit Parameters
Parameter |
Type |
Required |
Default |
Description |
onVerify |
Future<bool> Function(String) |
β
|
- |
Verify OTP callback |
onResend |
VoidCallback |
β
|
- |
Resend OTP callback |
fieldCount |
int |
β |
4 |
Number of OTP fields |
fieldConfig |
OtpFieldConfig? |
β |
null |
Field styling configuration |
animationConfig |
OtpAnimationConfig? |
β |
null |
Animation settings |
smsConfig |
OtpSmsConfig? |
β |
null |
SMS autofill configuration |
securityConfig |
OtpSecurityConfig? |
β |
null |
Security features |
performanceConfig |
OtpPerformanceConfig? |
β |
null |
Performance optimization |
errorConfig |
OtpErrorConfig? |
β |
null |
Error handling |
title |
String? |
β |
null |
Main title text |
subtitle |
String? |
β |
null |
Subtitle text |
primaryColor |
Color? |
β |
Colors.blue |
Primary theme color |
successColor |
Color? |
β |
Colors.green |
Success state color |
errorColor |
Color? |
β |
Colors.red |
Error state color |
OtpFieldConfig Parameters
Parameter |
Type |
Required |
Default |
Description |
fieldWidth |
double |
β |
55.125 |
Width of each OTP field |
fieldHeight |
double |
β |
60.731 |
Height of each OTP field |
borderRadius |
double |
β |
17.752 |
Border radius for rounded corners |
borderWidth |
double |
β |
1.869 |
Border width of the field |
primaryColor |
Color |
β |
Color(0xFF018CC3) |
Primary color for active states |
secondaryColor |
Color |
β |
Color(0xFF8B8B8B) |
Secondary color for inactive states |
backgroundColor |
Color |
β |
Colors.white |
Background color of fields |
fieldFontSize |
double |
β |
24.0 |
Font size for field text |
fieldFontWeight |
FontWeight |
β |
FontWeight.bold |
Font weight for field text |
letterSpacing |
double |
β |
0.5 |
Letter spacing for field text |
cursorColor |
Color? |
β |
null |
Color of the cursor |
cursorWidth |
double |
β |
1.0 |
Width of the cursor |
cursorStyle |
CursorStyle |
β |
CursorStyle.system |
Style of the cursor |
enableShadow |
bool |
β |
false |
Enable shadow effects |
shadowColor |
Color? |
β |
null |
Color of the shadow |
shadowBlurRadius |
double |
β |
10.0 |
Blur radius of the shadow |
fieldShape |
OtpFieldShape |
β |
OtpFieldShape.roundedRectangle |
Shape of the field |
enableHapticFeedback |
bool |
β |
false |
Enable haptic feedback |
OtpAnimationConfig Parameters
Parameter |
Type |
Required |
Default |
Description |
enableAnimation |
bool |
β |
false |
Enable/disable all animations |
animationDuration |
Duration |
β |
Duration(milliseconds: 300) |
Duration for main animations |
animationCurve |
Curve |
β |
Curves.easeInOut |
Animation curve |
enableFieldStateAnimation |
bool |
β |
false |
Enable field state transitions |
fieldTransitionDuration |
Duration |
β |
Duration(milliseconds: 150) |
Field transition duration |
fieldFillAnimationType |
FieldFillAnimationType |
β |
FieldFillAnimationType.scale |
Animation when field is filled |
errorFieldAnimationType |
ErrorFieldAnimationType |
β |
ErrorFieldAnimationType.shake |
Animation for error states |
errorShakeAmplitude |
double |
β |
4.0 |
Amplitude of shake animation |
enableCursorAnimation |
bool |
β |
false |
Enable cursor blinking |
cursorBlinkDuration |
Duration |
β |
Duration(milliseconds: 1000) |
Cursor blink duration |
OtpSmsConfig Parameters
Parameter |
Type |
Required |
Default |
Description |
enableSmsAutofill |
bool |
β |
false |
Enable SMS autofill |
enableSmartAuth |
bool |
β |
false |
Enable Smart Auth integration |
enableSmsRetrieverAPI |
bool |
β |
false |
Enable Android SMS Retriever API |
enableSmsUserConsentAPI |
bool |
β |
false |
Enable Android User Consent API |
appSignature |
String? |
β |
null |
App signature for SMS Retriever |
senderPhoneNumber |
String? |
β |
null |
Filter SMS by sender number |
enableSmsValidation |
bool |
β |
false |
Enable SMS content validation |
smsValidationRegex |
String? |
β |
null |
Regex pattern for SMS validation |
smsTimeout |
Duration |
β |
Duration(minutes: 5) |
Timeout for SMS listening |
enableSmsErrorHandling |
bool |
β |
false |
Enable SMS error handling |
OtpSecurityConfig Parameters
Parameter |
Type |
Required |
Default |
Description |
enableRateLimiting |
bool |
β |
false |
Enable rate limiting |
maxAttemptsPerMinute |
int |
β |
5 |
Max attempts per minute |
maxAttemptsPerHour |
int |
β |
20 |
Max attempts per hour |
lockoutDuration |
Duration |
β |
Duration(minutes: 5) |
Lockout duration after max attempts |
enableBiometricIntegration |
bool |
β |
false |
Enable biometric authentication |
biometricTimeout |
Duration |
β |
Duration(seconds: 30) |
Biometric authentication timeout |
enableAdvancedValidation |
bool |
β |
false |
Enable advanced OTP validation |
validationPattern |
String? |
β |
null |
Regex pattern for validation |
enableEncryption |
bool |
β |
false |
Enable OTP encryption |
enableAuditLogging |
bool |
β |
false |
Enable audit logging |
enableSessionManagement |
bool |
β |
false |
Enable session management |
sessionTimeout |
Duration |
β |
Duration(minutes: 30) |
Session timeout duration |
OtpErrorConfig Parameters
Parameter |
Type |
Required |
Default |
Description |
maxErrorRetries |
int |
β |
3 |
Maximum error retries allowed |
enableFieldLockout |
bool |
β |
false |
Lock fields after max errors |
fieldLockoutDuration |
Duration |
β |
Duration(seconds: 30) |
Field lockout duration |
clearFieldsOnError |
bool |
β |
false |
Clear fields on error |
errorShakeEffect |
bool |
β |
false |
Enable shake effect on error |
errorShakeDuration |
Duration |
β |
Duration(milliseconds: 500) |
Duration of shake animation |
errorShakeCount |
int |
β |
3 |
Number of shakes |
enableHapticFeedbackOnError |
bool |
β |
false |
Haptic feedback on error |
errorHapticFeedbackType |
ErrorHapticFeedbackType |
β |
ErrorHapticFeedbackType.medium |
Type of haptic feedback |
showErrorIcon |
bool |
β |
false |
Show error icon |
errorIcon |
IconData? |
β |
null |
Custom error icon |
autoClearErrorOnInput |
bool |
β |
true |
Auto-clear error on new input |
Parameter |
Type |
Required |
Default |
Description |
enableLazyLoading |
bool |
β |
false |
Enable lazy loading of fields |
maxVisibleFields |
int |
β |
10 |
Maximum visible fields at once |
enableMemoryOptimization |
bool |
β |
false |
Enable memory optimization |
enableAnimationPooling |
bool |
β |
false |
Pool animations for reuse |
maxAnimationPoolSize |
int |
β |
10 |
Maximum animation pool size |
animationCleanupDelay |
Duration |
β |
Duration(seconds: 5) |
Delay before animation cleanup |
enableFieldRecycling |
bool |
β |
false |
Recycle field widgets |
enableBackgroundCleanup |
bool |
β |
false |
Enable background cleanup |
cleanupInterval |
Duration |
β |
Duration(minutes: 1) |
Background cleanup interval |
enablePerformanceMonitoring |
bool |
β |
false |
Monitor performance metrics |
enableMemoryLeakDetection |
bool |
β |
false |
Detect memory leaks |
Parameter |
Type |
Required |
Default |
Description |
index |
int |
β
|
- |
Index of the field |
value |
String? |
β |
null |
Current value of the field |
config |
OtpFieldConfig |
β
|
- |
Field configuration |
animationConfig |
OtpAnimationConfig? |
β |
null |
Animation configuration |
focusNode |
FocusNode? |
β |
null |
Focus node for the field |
onChanged |
ValueChanged<String>? |
β |
null |
Callback when value changes |
onCompleted |
VoidCallback? |
β |
null |
Callback when field is completed |
isError |
bool |
β |
false |
Whether field is in error state |
isSuccess |
bool |
β |
false |
Whether field is in success state |
isActive |
bool |
β |
false |
Whether field is currently active |
π€ Contributing
We welcome contributions! Please see our Contributing Guide for details.
Development Setup
# Clone the repository
git clone https://github.com/seifmoustafa/flutter_otp_kit.git
# Install dependencies
cd flutter_otp_kit
flutter pub get
# Run example
cd example
flutter run
Running Tests
flutter test
π License
This project is licensed under the MIT License - see the LICENSE file for details.
π Support
If you find this package helpful, please:
- β Star the repository on GitHub
- π Like the package on pub.flutter-io.cn
- π Report issues on GitHub
- π‘ Suggest features via GitHub discussions
- β Buy me a coffee if you'd like to support development

Made with β€οΈ for the Flutter Community
Flutter OTP Kit - The most comprehensive OTP verification solution