expo-auto-sms-verification
v0.1.1
Published
Expo module for Android SMS Retriever API — automatically intercepts OTP SMS messages without requiring SMS read permissions.
Downloads
25
Maintainers
Readme
expo-auto-sms-verification
An Expo module for Android SMS Retriever API that automatically intercepts OTP SMS messages without requiring SMS read permissions.
Uses Google's SMS Retriever API under the hood, so your app never needs the READ_SMS permission.
Platform support: Android only. iOS does not have an equivalent API. All the methods throw an
UnavailabilityErroron iOS.
Expo Go: This module includes native code and does not work in Expo Go. You must use a development build (
npx expo run:android) or an EAS build.
Why Use This?
- No permissions required: Unlike traditional SMS reading, the SMS Retriever API works without
READ_SMSor any runtime permission. No permission dialogs, no Play Store warnings. - Won't get your app rejected on Google Play: Google treats
READ_SMSandRECEIVE_SMSas restricted permissions. Apps that declare them must submit a Permissions Declaration Form and demonstrate that SMS access is core to the app's functionality — most OTP use cases do not qualify. Apps that fail this review get rejected or removed from the Play Store. This module avoids the problem entirely by using the SMS Retriever API, which is Google's recommended approach for OTP verification and requires zero dangerous permissions. - Ready-to-use React hook:
useAutoSmsVerificationhandles the entire OTP flow out of the box: listening, extraction, state management, and cleanup. Drop it into your component and you're done. - Seamless user experience: OTPs are intercepted and filled automatically without the user having to switch apps or manually copy codes (well this is kind of obvious).
Table of Contents
- Why Use This?
- Installation
- Quick Start
- How It Works
- SMS Message Format
- API Reference
- Example
- Testing
- Troubleshooting
- Contributing
- License
Installation
npm install expo-auto-sms-verificationNo additional native configuration is required. The module uses Google Play Services (play-services-auth and play-services-auth-api-phone), which are bundled automatically.
Quick Start
import { useAutoSmsVerification } from 'expo-auto-sms-verification';
function OtpScreen() {
const { otp, status, error, startListening, stopListening } =
useAutoSmsVerification({
otpPattern: /(\d{6})/, // match a 6-digit OTP
onOtpReceived: (code) => {
console.log('OTP received:', code);
// submit OTP to your backend
},
});
return (
<View>
<Text>Status: {status}</Text>
<Text>OTP: {otp ?? '—'}</Text>
<Button title="Start Listening" onPress={startListening} />
<Button title="Stop" onPress={stopListening} />
</View>
);
}How It Works
- Your app calls
startSmsRetriever()(or the hook'sstartListening()). - Google Play Services begins listening for an incoming SMS that contains your app's message hash.
- When a matching SMS arrives, the module fires an
onSmsReceivedevent with the full message text. - The
useAutoSmsVerificationhook extracts the OTP using the regex pattern you provide. - The listener automatically stops after receiving an SMS or after a 5-minute timeout (Google's limit).
No SMS permission is needed, the SMS Retriever API delegates interception to Google Play Services.
SMS Message Format
For the SMS Retriever API to recognize your message, the SMS sent from your backend must follow this format:
Your verification code is 123456.
FA+9qCX9VSuRequirements:
- The message must be no longer than 140 bytes
- It must end with an 11-character hash that identifies your app
- The hash is derived from your app's package name and signing certificate
- More about verification message conventions here.
Getting Your App Hash
Call getMessageHash() to retrieve the hash(es) for your app:
import ExpoAutoSmsVerification from 'expo-auto-sms-verification';
const hashes = await ExpoAutoSmsVerification.getMessageHash();
console.log(hashes); // e.g. ['FA+9qCX9VSu']- Returns an array because your app may have multiple signing certificates (e.g. during Google Play certificate rotation)
- Send all hashes to your backend and append them to outgoing SMS messages
- The hash will differ between debug and release builds (different signing keys)
API Reference
useAutoSmsVerification(options?)
React hook that manages the full SMS verification lifecycle.
Options:
| Parameter | Type | Default | Description |
|---|---|---|---|
| otpPattern | RegExp | /(\d{4,8})/ | Regex with a capture group to extract the OTP from the SMS |
| onOtpReceived | (otp: string) => void | — | Callback fired immediately when an OTP is extracted |
Returns:
| Property | Type | Description |
|---|---|---|
| startListening | () => Promise<void> | Start the SMS retriever |
| stopListening | () => Promise<void> | Stop the SMS retriever early |
| otp | string \| null | The extracted OTP, or null |
| status | SmsVerificationStatus | One of: 'idle', 'listening', 'received', 'timeout', 'error' |
| error | string \| null | Error message, if any |
Native Module Methods
Available via the default export:
import ExpoAutoSmsVerification from 'expo-auto-sms-verification';startSmsRetriever(): Promise<void>
Starts the SMS Retriever listener. Registers a BroadcastReceiver that listens for up to 5 minutes.
stopSmsRetriever(): Promise<void>
Stops the listener and unregisters the BroadcastReceiver.
getMessageHash(): Promise<string[]>
Returns the 11-character hash string(s) for your app's signing certificate(s). Pass these to your backend to include in SMS messages.
Events
If using the native module directly (without the hook), you can subscribe to events:
import ExpoAutoSmsVerification from 'expo-auto-sms-verification';
const sub1 = ExpoAutoSmsVerification.addListener('onSmsReceived', ({ message }) => {
console.log('SMS:', message);
});
const sub2 = ExpoAutoSmsVerification.addListener('onSmsTimeout', () => {
console.log('Timed out after 5 minutes');
});
const sub3 = ExpoAutoSmsVerification.addListener('onSmsError', ({ error }) => {
console.error('Error:', error);
});
// Clean up
sub1.remove();
sub2.remove();
sub3.remove();Types
type SmsReceivedPayload = {
message: string;
};
type SmsErrorPayload = {
error: string;
};
type SmsVerificationStatus = 'idle' | 'listening' | 'received' | 'timeout' | 'error';
type UseAutoSmsVerificationOptions = {
otpPattern?: RegExp;
onOtpReceived?: (otp: string) => void;
};
type UseAutoSmsVerificationResult = {
startListening: () => Promise<void>;
stopListening: () => Promise<void>;
otp: string | null;
status: SmsVerificationStatus;
error: string | null;
};Example
A full working example app is included in the example/ folder. It demonstrates the hook usage, OTP auto-fill, status handling, and displaying the required SMS format with your app hash.
To run it:
cd example
npm install
npx expo run:androidTesting
To test locally, send an SMS to the emulator or device in this exact format:
Your OTP is 123456.
<your-app-hash>Get <your-app-hash> by calling getMessageHash() in your debug build.
Tip: The hash differs between debug and release builds because they use different signing certificates.
Troubleshooting
| Issue | Solution |
|---|---|
| SMS not detected | Ensure the SMS ends with the correct app hash from getMessageHash() |
| Timeout after 5 minutes | This is Google's hard limit, the user must request a new OTP |
| Different hash in production | Release builds use a different signing key, call getMessageHash() in the production environment and update your backend |
| UnavailabilityError on iOS | This is expected, SMS Retriever API is Android-only |
| Google Play Services missing | The device must have Google Play Services installed (most Android devices do, except some custom ROMs or China-market devices) |
Contributing
Contributions are welcome! Please open an issue or submit a pull request on GitHub.
License
MIT
