SMS Retriever API Guide
Learn how to implement Android's SMS Retriever API for automatic SMS consumption in Flutter, React Native, and Kotlin applications.
Overview
The Android SMS Retriever API allows your app to automatically read SMS messages that contain a specific 11-character hash string. This enables seamless OTP verification and other SMS-based authentication flows without requiring SMS read permissions.
When you include a hash in your SMS Gateway API request, the hash will be automatically appended to your message with a space separator, making it compatible with the SMS Retriever API.
How It Works
- Generate Hash: Your app generates an 11-character hash based on your app's package name and signing certificate
- Send SMS: Include the hash in your SMS Gateway API request
- Automatic Consumption: Android automatically reads SMS messages containing your hash
- Receive SMS: Your app receives the SMS content without user interaction
Hash Format
- Length: Exactly 11 characters
- Characters: Alphanumeric (A-Z, a-z, 0-9)
- Example:
ABC123XYZ45 - Separator: The hash is automatically added on a new line after your message
Example: If you send message: "Your code is: 123456" with hash: "ABC123XYZ45", the resulting SMS will be:
Your code is: 123456
ABC123XYZ45
Implementation Guides
Flutter
Using sms_autofill Package
- Add dependency to
pubspec.yaml:
dependencies:
sms_autofill: ^2.3.0
- Get the hash:
import 'package:sms_autofill/sms_autofill.dart';
class SmsRetrieverExample {
Future<String?> getAppSignature() async {
try {
final signature = await SmsAutoFill().getAppSignature;
return signature;
} catch (e) {
print('Error getting app signature: $e');
return null;
}
}
}
- Listen for SMS:
import 'package:sms_autofill/sms_autofill.dart';
class OtpVerificationScreen extends StatefulWidget {
@override
_OtpVerificationScreenState createState() => _OtpVerificationScreenState();
}
class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
String? _code;
final SmsAutoFill _autoFill = SmsAutoFill();
@override
void initState() {
super.initState();
_listenForSms();
}
void _listenForSms() {
_autoFill.listenForCode();
}
@override
Widget build(BuildContext context) {
return PinFieldAutoFill(
codeLength: 6,
onCodeSubmitted: (code) {
setState(() {
_code = code;
});
// Verify OTP with your backend
_verifyOtp(code);
},
);
}
Future<void> _verifyOtp(String code) async {
// Send verification request to your backend
final response = await http.post(
Uri.parse('https://your-api.com/otp/verify'),
body: jsonEncode({
'phoneNumber': '+1234567890',
'otp': code,
}),
);
}
@override
void dispose() {
_autoFill.unregisterListener();
super.dispose();
}
}
- Send SMS with hash:
import 'package:http/http.dart' as http;
import 'dart:convert';
Future<void> sendOtpWithHash(String phoneNumber) async {
// Get the app signature (hash)
final signature = await SmsAutoFill().getAppSignature;
// Send SMS via SMS Gateway API
final response = await http.post(
Uri.parse('https://sms.chinqit.com/messages/send'),
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'YOUR_API_KEY',
},
body: jsonEncode({
'phoneNumber': phoneNumber,
'message': 'Your verification code is: 123456',
'hash': signature, // Include the hash
}),
);
if (response.statusCode == 200) {
print('SMS sent successfully');
}
}
Using sms_retriever Package (Alternative)
dependencies:
sms_retriever: ^1.0.0
import 'package:sms_retriever/sms_retriever.dart';
Future<String?> getAppSignature() async {
try {
final signature = await SmsRetriever.getAppSignature();
return signature;
} catch (e) {
print('Error: $e');
return null;
}
}
void listenForSms() {
SmsRetriever.listenForSms().listen((sms) {
// Extract OTP from SMS
final otp = extractOtp(sms);
// Verify OTP
verifyOtp(otp);
});
}
String extractOtp(String sms) {
// Extract 6-digit OTP from SMS
final regex = RegExp(r'\b\d{6}\b');
final match = regex.firstMatch(sms);
return match?.group(0) ?? '';
}
React Native
Using react-native-sms-retriever
- Install package:
npm install react-native-sms-retriever
# or
yarn add react-native-sms-retriever
- Link the package (for older React Native versions):
cd ios && pod install
- Get the hash:
import SmsRetriever from "react-native-sms-retriever";
class SmsRetrieverExample {
async getAppSignature() {
try {
const hash = await SmsRetriever.getAppHash();
return hash;
} catch (error) {
console.error("Error getting app hash:", error);
return null;
}
}
}
- Listen for SMS:
import SmsRetriever from "react-native-sms-retriever";
import { useEffect, useState } from "react";
function OtpVerificationScreen() {
const [otp, setOtp] = useState("");
useEffect(() => {
// Start listening for SMS
SmsRetriever.startSmsRetriever()
.then(() => {
console.log("SMS Retriever started");
})
.catch((error) => {
console.error("Error starting SMS Retriever:", error);
});
// Listen for SMS
const subscription = SmsRetriever.addSmsListener((event) => {
const message = event.message;
// Extract OTP from message
const extractedOtp = extractOtp(message);
if (extractedOtp) {
setOtp(extractedOtp);
verifyOtp(extractedOtp);
}
});
return () => {
// Cleanup
subscription.remove();
SmsRetriever.removeSmsListener();
};
}, []);
const extractOtp = (message) => {
// Extract 6-digit OTP
const match = message.match(/\b\d{6}\b/);
return match ? match[0] : null;
};
const verifyOtp = async (code) => {
try {
const response = await fetch("https://your-api.com/otp/verify", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
phoneNumber: "+1234567890",
otp: code,
}),
});
const data = await response.json();
if (data.verified) {
console.log("OTP verified successfully");
}
} catch (error) {
console.error("Error verifying OTP:", error);
}
};
return (
<View>
<Text>Enter OTP: {otp}</Text>
</View>
);
}
- Send SMS with hash:
import SmsRetriever from "react-native-sms-retriever";
async function sendOtpWithHash(phoneNumber) {
try {
// Get the app hash
const hash = await SmsRetriever.getAppHash();
// Send SMS via SMS Gateway API
const response = await fetch("https://sms.chinqit.com/messages/send", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "YOUR_API_KEY",
},
body: JSON.stringify({
phoneNumber: phoneNumber,
message: "Your verification code is: 123456",
hash: hash, // Include the hash
}),
});
const data = await response.json();
if (data.success) {
console.log("SMS sent successfully");
}
} catch (error) {
console.error("Error sending SMS:", error);
}
}
Using react-native-otp-verify (Alternative)
npm install react-native-otp-verify
import OtpVerify from "react-native-otp-verify";
// Get hash
OtpVerify.getHash()
.then((hash) => {
console.log("Hash:", hash);
})
.catch((error) => {
console.error("Error:", error);
});
// Listen for SMS
OtpVerify.getOtp()
.then((message) => {
// Extract OTP from message
const otp = extractOtp(message);
verifyOtp(otp);
})
.catch((error) => {
console.error("Error:", error);
});
Kotlin (Native Android)
Implementation Steps
- Add dependency to
build.gradle.kts:
dependencies {
implementation("com.google.android.gms:play-services-auth-api-phone:18.0.1")
}
- Get the hash:
import com.google.android.gms.auth.api.phone.SmsRetriever
import com.google.android.gms.auth.api.phone.SmsRetrieverClient
import com.google.android.gms.tasks.Task
class SmsRetrieverHelper(private val context: Context) {
fun getAppSignatureHash(callback: (String?) -> Unit) {
val client: SmsRetrieverClient = SmsRetriever.getClient(context)
val task: Task<String> = client.smsRetrieverClient.startSmsRetriever()
task.addOnSuccessListener { result ->
// The hash is returned in the result
// Note: This method doesn't directly return the hash
// You need to use the SMS Retriever API differently
callback(null)
}
task.addOnFailureListener { e ->
callback(null)
}
}
// Alternative: Get hash using reflection (for testing)
fun getAppSignatureHashString(): String? {
return try {
val info = context.packageManager.getPackageInfo(
context.packageName,
PackageManager.GET_SIGNATURES
)
val signatures = info.signatures
if (signatures.isNotEmpty()) {
val signature = signatures[0]
val md = MessageDigest.getInstance("SHA-256")
md.update(signature.toByteArray())
val hashBytes = md.digest()
// Convert to base64 and take first 11 characters
Base64.encodeToString(hashBytes, Base64.NO_WRAP)
.substring(0, 11)
.replace("+", "A")
.replace("/", "B")
.replace("=", "C")
} else {
null
}
} catch (e: Exception) {
null
}
}
}
- Listen for SMS:
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import com.google.android.gms.auth.api.phone.SmsRetriever
import com.google.android.gms.auth.api.phone.SmsRetrieverClient
import com.google.android.gms.common.api.CommonStatusCodes
import com.google.android.gms.common.api.Status
class SmsRetrieverReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
val extras = intent.extras
val status = extras?.get(SmsRetriever.EXTRA_STATUS) as? Status
when (status?.statusCode) {
CommonStatusCodes.SUCCESS -> {
// Get SMS message
val message = extras.getString(SmsRetriever.EXTRA_SMS_MESSAGE)
message?.let {
// Extract OTP from message
val otp = extractOtp(it)
otp?.let { code ->
// Verify OTP
verifyOtp(code)
}
}
}
CommonStatusCodes.TIMEOUT -> {
// Timeout waiting for SMS
}
}
}
}
private fun extractOtp(message: String): String? {
// Extract 6-digit OTP
val regex = Regex("\\b\\d{6}\\b")
return regex.find(message)?.value
}
private fun verifyOtp(otp: String) {
// Send verification request to your backend
// Implementation depends on your networking library
}
}
- Start SMS Retriever:
import com.google.android.gms.auth.api.phone.SmsRetriever
import com.google.android.gms.auth.api.phone.SmsRetrieverClient
class MainActivity : AppCompatActivity() {
private lateinit var smsRetrieverReceiver: BroadcastReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Register SMS Retriever receiver
smsRetrieverReceiver = SmsRetrieverReceiver()
val intentFilter = IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION)
registerReceiver(smsRetrieverReceiver, intentFilter)
// Start SMS Retriever
startSmsRetriever()
}
private fun startSmsRetriever() {
val client: SmsRetrieverClient = SmsRetriever.getClient(this)
val task = client.startSmsRetriever()
task.addOnSuccessListener {
// SMS Retriever started successfully
Log.d("SMS", "SMS Retriever started")
}
task.addOnFailureListener { e ->
Log.e("SMS", "Failed to start SMS Retriever", e)
}
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(smsRetrieverReceiver)
}
}
- Send SMS with hash:
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
class SmsService(private val context: Context) {
private val client = OkHttpClient()
suspend fun sendOtpWithHash(phoneNumber: String, message: String) {
// Get hash
val hash = getAppSignatureHashString()
// Prepare request
val json = JSONObject().apply {
put("phoneNumber", phoneNumber)
put("message", message)
hash?.let { put("hash", it) }
}
val requestBody = json.toString()
.toRequestBody("application/json".toMediaType())
val request = Request.Builder()
.url("https://sms.chinqit.com/messages/send")
.post(requestBody)
.addHeader("Content-Type", "application/json")
.addHeader("X-API-Key", "YOUR_API_KEY")
.build()
try {
val response = client.newCall(request).execute()
if (response.isSuccessful) {
Log.d("SMS", "SMS sent successfully")
}
} catch (e: Exception) {
Log.e("SMS", "Error sending SMS", e)
}
}
private fun getAppSignatureHashString(): String? {
// Implementation from step 2
return SmsRetrieverHelper(context).getAppSignatureHashString()
}
}
Important Notes
-
Hash Generation: The hash is generated based on your app's package name and signing certificate. It will be different for debug and release builds.
-
SMS Format: The SMS must start with the hash string. Our API appends the hash to the end of your message, so ensure your message format works with this.
-
Testing: Use the SMS Retriever API Playground to test your hash generation.
-
Permissions: No SMS read permissions are required. The SMS Retriever API handles SMS reading automatically.
-
Timeout: The SMS Retriever API has a 5-minute timeout. If no SMS is received within this time, you'll need to restart the retriever.
Troubleshooting
Hash Not Working
- Verify your hash is exactly 11 characters
- Ensure you're using the correct signing certificate (debug vs release)
- Check that the SMS format matches Android's requirements
SMS Not Received
- Verify the hash matches your app's signature
- Ensure SMS Retriever is started before sending the SMS
- Check that the SMS contains the hash string
Timeout Issues
- Restart SMS Retriever if timeout occurs
- Implement retry logic for failed attempts
- Consider fallback to manual OTP entry
Additional Resources
- Android SMS Retriever API Documentation
- SMS Retriever API Playground
- Flutter sms_autofill Package
- React Native SMS Retriever
Need help? Check the API Reference or Troubleshooting Guide for more information.