Cuộc gọi chớp nhoáng
Flash Call là phương thức xác minh số điện thoại sử dụng cuộc gọi nhỡ thay vì SMS để xác minh số điện thoại. Nó nhanh hơn, an toàn hơn và tiết kiệm chi phí hơn.
Tổng quan
Xác minh Cuộc gọi Flash hoạt động bằng cách:
- Người dùng yêu cầu xác minh
- Hệ thống thực hiện cuộc gọi đến điện thoại của người dùng
- Cuộc gọi tự động kết thúc sau 1-2 hồi chuông
- Ứng dụng của người dùng ghi lại ID người gọi
- ID người gọi được xác minh theo mẫu dự kiến
- Người dùng được xác thực
Lợi ích
Tiết kiệm chi phí
- Rẻ hơn tới 10 lần so với SMS
- Không tính phí gửi tin nhắn
- Giảm chi phí cho việc xác minh số lượng lớn
Nhanh hơn
- Xác minh tức thì (1-3 giây)
- Không phải chờ gửi SMS
- Trải nghiệm người dùng tốt hơn
An toàn hơn
- Khó chặn hơn SMS
- Không có OTP hiển thị trong thông báo
- Chống lại các cuộc tấn công hoán đổi SIM
Phạm vi tiếp cận toàn cầu
- Hoạt động ở các quốc gia có hạn chế về SMS
- Không có vấn đề với việc lọc SMS
- Khả năng tương thích điện thoại phổ thông
Cuộc gọi flash cơ bản
Yêu cầu
{
"from": "YourApp",
"to": "+380XXXXXXXXX",
"type": "flashcall",
"messageData": {
"callerId": "+380123456789"
}
}
Thông số
| Tham số | Loại | Bắt buộc | Mô tả |
|---|---|---|---|
từ |
chuỗi | Có | Mã định danh người gửi của bạn |
đến |
chuỗi | Có | Số điện thoại người nhận (E.164) |
loại |
chuỗi | Có | Đặt thành "flashcall" |
Id người gọi |
chuỗi | Có | Số điện thoại sẽ gọi cho người dùng |
ttl |
số nguyên | Không | Thời gian tồn tại tính bằng giây (mặc định: 60) |
Nó hoạt động như thế nào
1. Người dùng nhập số điện thoại
Người dùng cung cấp số điện thoại của họ trong ứng dụng của bạn:
2. Yêu cầu cuộc gọi Flash
Máy chủ của bạn yêu cầu xác minh cuộc gọi flash:
curl -X POST https://restapi.smsbat.com/bat/messagelist \
-H "X-Authorization-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"messages": [{
"from": "YourApp",
"to": "+380XXXXXXXXX",
"type": "flashcall",
"messageData": {
"callerId": "+380123456789"
},
"ttl": 60
}]
}'
3. Phản hồi API
API trả về mẫu ID người gọi dự kiến:
{
"messagelistId": 123456,
"messages": [
{
"messageId": "abc123def456",
"status": "accepted",
"callerId": "+380123456789",
"pattern": "***456789",
"to": "+380XXXXXXXXX"
}
]
}
4. Bắt đầu cuộc gọi
Hệ thống bắt đầu cuộc gọi đến điện thoại của người dùng và kết thúc sau 1-2 hồi chuông.
5. Chụp ID người gọi
Ứng dụng của người dùng ghi lại ID người gọi của cuộc gọi đến:
// Android example
val cursor = contentResolver.query(
CallLog.Calls.CONTENT_URI,
arrayOf(CallLog.Calls.NUMBER),
null, null,
CallLog.Calls.DATE + " DESC"
)
6. Xác minh mẫu
So sánh ID người gọi đã chụp với mẫu dự kiến:
// JavaScript example
function verifyFlashCall(callerId, pattern) {
// Remove non-digits
const callerDigits = callerId.replace(/\D/g, '');
const patternDigits = pattern.replace(/\*/g, '.');
// Check if matches pattern
const regex = new RegExp(patternDigits);
return regex.test(callerDigits);
}
Ví dụ triển khai
Android
class FlashCallVerification {
fun requestFlashCall(phoneNumber: String) {
// 1. Request flash call from API
val response = api.requestFlashCall(phoneNumber)
val pattern = response.pattern
// 2. Wait for incoming call
val callReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == TelephonyManager.ACTION_PHONE_STATE_CHANGED) {
val state = intent.getStringExtra(TelephonyManager.EXTRA_STATE)
if (state == TelephonyManager.EXTRA_STATE_RINGING) {
val callerId = intent.getStringExtra(
TelephonyManager.EXTRA_INCOMING_NUMBER
)
// 3. Verify caller ID against pattern
if (verifyPattern(callerId, pattern)) {
onVerificationSuccess()
}
}
}
}
}
// Register receiver
context.registerReceiver(
callReceiver,
IntentFilter(TelephonyManager.ACTION_PHONE_STATE_CHANGED)
)
}
private fun verifyPattern(callerId: String?, pattern: String): Boolean {
if (callerId == null) return false
val regex = pattern.replace("*", "\\d").toRegex()
return regex.matches(callerId)
}
}
iOS
class FlashCallVerification {
func requestFlashCall(phoneNumber: String) {
// 1. Request flash call from API
api.requestFlashCall(phoneNumber) { response in
let pattern = response.pattern
// 2. Use CallKit to detect incoming call
let provider = CXProvider(configuration: providerConfiguration)
provider.setDelegate(self, queue: nil)
// Store pattern for verification
self.expectedPattern = pattern
}
}
// CallKit delegate
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
// Capture caller ID
let callerId = action.callUUID.uuidString
// Verify against pattern
if verifyPattern(callerId: callerId, pattern: expectedPattern) {
onVerificationSuccess()
}
action.fulfill()
}
private func verifyPattern(callerId: String, pattern: String) -> Bool {
let regex = try! NSRegularExpression(
pattern: pattern.replacingOccurrences(of: "*", with: "\\d")
)
let range = NSRange(location: 0, length: callerId.count)
return regex.firstMatch(in: callerId, range: range) != nil
}
}
Web (Phía máy chủ)
// Node.js example
const express = require('express');
const app = express();
app.post('/request-verification', async (req, res) => {
const { phoneNumber } = req.body;
// 1. Request flash call
const response = await fetch('https://restapi.smsbat.com/bat/messagelist', {
method: 'POST',
headers: {
'X-Authorization-Key': process.env.SMSBAT_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
messages: [{
from: 'YourApp',
to: phoneNumber,
type: 'flashcall',
messageData: {
callerId: process.env.FLASH_CALL_NUMBER
},
ttl: 60
}]
})
});
const data = await response.json();
const { messageId, pattern } = data.messages[0];
// 2. Store pattern for verification
await redis.setex(`flashcall:${messageId}`, 60, pattern);
// 3. Return pattern to client
res.json({ messageId, pattern });
});
app.post('/verify-flashcall', async (req, res) => {
const { messageId, callerId } = req.body;
// 1. Get expected pattern
const pattern = await redis.get(`flashcall:${messageId}`);
if (!pattern) {
return res.status(400).json({ error: 'Verification expired' });
}
// 2. Verify caller ID
const regex = new RegExp(pattern.replace(/\*/g, '\\d'));
const isValid = regex.test(callerId);
if (isValid) {
// Mark phone as verified
await markPhoneVerified(callerId);
res.json({ verified: true });
} else {
res.status(400).json({ error: 'Invalid caller ID' });
}
});
Định dạng phản hồi
Phản hồi thành công
{
"messagelistId": 123456,
"messages": [
{
"messageId": "abc123def456",
"status": "accepted",
"callerId": "+380123456789",
"pattern": "***456789",
"to": "+380XXXXXXXXX",
"ttl": 60
}
]
}
Trường phản hồi
| Lĩnh vực | Loại | Mô tả |
|---|---|---|
messageId |
chuỗi | ID xác minh duy nhất |
trạng thái |
chuỗi | Trạng thái: được chấp nhận, bị từ chối |
Id người gọi |
chuỗi | Số ID người gọi đầy đủ |
mẫu |
chuỗi | Mẫu phù hợp (chữ số + dấu hoa thị) |
đến |
chuỗi | Số điện thoại người nhận |
ttl |
số nguyên | Thời hạn hiệu lực tính bằng giây |
Khớp mẫu
API trả về một mẫu có dấu hoa thị che một số chữ số:
Ứng dụng của bạn nên:
- Chụp ID người gọi đến
- Trích xuất chữ số từ ID người gọi
- So khớp với mẫu (dấu hoa thị = bất kỳ chữ số nào)
- Xác minh sự trùng khớp trong khoảng thời gian TTL
Chuyển sang SMS
Nếu Flash Call không thành công, tự động chuyển về SMS:
{
"from": "YourApp",
"to": "+380XXXXXXXXX",
"type": "flashcall",
"messageData": {
"callerId": "+380123456789"
},
"fallback": {
"type": "sms",
"text": "Your verification code is: 123456"
},
"ttl": 60
}
Trường hợp sử dụng
Đăng ký tài khoản
Xác minh số điện thoại trong quá trình đăng ký mà không mất phí SMS.
Xác minh đăng nhập
Xác thực hai yếu tố bằng cuộc gọi flash.
Cập nhật số điện thoại
Xác minh số điện thoại mới khi người dùng cập nhật hồ sơ.
Xác nhận giao dịch
Xác nhận các giao dịch có giá trị cao bằng cuộc gọi flash.
Các phương pháp hay nhất
TTL
- ✅ Đặt TTL thành 60-90 giây
- ✅ Cho phép người dùng thử lại sau khi hết hạn
- ❌ Không sử dụng TTL dài hơn 120 giây
Trải nghiệm người dùng
- Hiển thị thông báo “Đang chờ cuộc gọi…”
- Hiển thị đồng hồ đếm ngược (60 giây)
- Cung cấp tùy chọn "Sử dụng SMS thay thế"
- Tự động phát hiện và xác minh ID người gọi
Xử lý lỗi
- Xử lý quyền truy cập điện thoại bị thiếu
- Hết thời gian chờ sau khi TTL hết hạn
- Cung cấp tùy chọn dự phòng SMS
- Hiển thị thông báo lỗi rõ ràng
Quyền
Yêu cầu quyền của điện thoại trước cuộc gọi flash:
Android:
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
iOS:
Thử nghiệm
- Kiểm tra trên các thiết bị khác nhau
- Thử nghiệm với các nhà mạng khác nhau
- Kịch bản từ chối quyền kiểm tra
- Kiểm tra các kịch bản hết thời gian mạng
Hạn chế
Hỗ trợ nền tảng
- Hoạt động trên tất cả các thiết bị di động
- Yêu cầu khả năng gọi điện thoại
- Cần có quyền READ_PHONE_STATE
- Có thể không hoạt động trên máy tính bảng mà không có điện thoại
Mạng
- Yêu cầu kết nối điện thoại hoạt động
- Có thể thất bại trong điều kiện mạng kém
- Có thể áp dụng hạn chế của nhà cung cấp dịch vụ
- Giá quốc tế có thể thay đổi
Quyền riêng tư
- Người dùng có thể chặn các số chưa biết
- Một số máy có tính năng chặn cuộc gọi
- Yêu cầu quyền rõ ràng
- Xem xét mối quan ngại về quyền riêng tư của người dùng
Khắc phục sự cố
Không nhận được cuộc gọi
- Kiểm tra điện thoại có tín hiệu
- Xác minh định dạng số (E.164)
- Kiểm tra các hạn chế của nhà cung cấp dịch vụ
- Thử dự phòng SMS
Mẫu không khớp
- Đảm bảo chụp chính xác ID người gọi
- Loại bỏ các ký tự không có chữ số
- Kiểm tra định dạng mẫu
- Xác minh trong khoảng thời gian TTL
Quyền bị từ chối
- Yêu cầu quyền đúng cách
- Giải thích tại sao cần có quyền
- Cung cấp thay thế (SMS)
- Xử lý khéo léo
Các bước tiếp theo
- Viber OTP - Phân phối OTP thay thế
- Tin nhắn SMS - Dự phòng SMS
- Kiểm tra trạng thái - Theo dõi trạng thái cuộc gọi flash