플래시콜
플래시콜은 SMS 대신 부재중 전화를 이용해 전화번호를 인증하는 전화인증 방식이다. 더 빠르고 안전하며 비용 효율적입니다.
개요
플래시 통화 확인은 다음과 같이 작동합니다.
- 사용자가 확인을 요청합니다.
- 시스템이 사용자의 전화로 통화를 시작합니다.
- 벨이 1~2회 울린 후 자동으로 통화가 종료됩니다.
- 사용자의 앱이 발신자 ID를 캡처합니다.
- 발신자 ID는 예상 패턴에 대해 확인됩니다.
- 사용자가 인증되었습니다
혜택
비용 효율적
- SMS보다 최대 10배 저렴
- 메시지 전달 수수료 없음
- 대용량 검증에 따른 비용 절감
더 빠르게
- 즉시확인(1~3초)
- SMS 전달을 기다리지 않습니다.
- 더 나은 사용자 경험
더욱 안전하게
- SMS보다 차단하기가 더 어렵습니다.
- 알림에 OTP가 표시되지 않습니다.
- SIM 스왑 공격에 강함
글로벌 도달범위
- SMS 제한이 있는 국가에서 작동
- SMS 필터링에는 문제가 없습니다.
- 범용 전화 호환성
기본 플래시 통화
요청
{
"from": "YourApp",
"to": "+380XXXXXXXXX",
"type": "flashcall",
"messageData": {
"callerId": "+380123456789"
}
}
매개변수
| 매개변수 | 유형 | 필수 | 설명 |
|---|---|---|---|
| '에서' | 문자열 | 예 | 발신자 식별자 |
| '에' | 문자열 | 예 | 수신자 전화번호(E.164) |
| '유형' | 문자열 | 예 | "플래시콜"로 설정 |
발신자 ID |
문자열 | 예 | 사용자에게 전화할 전화번호 |
ttl |
정수 | 아니요 | TTL(초)(기본값: 60) |
작동 방식
1. 사용자가 전화번호를 입력합니다.
사용자는 앱에 자신의 전화번호를 제공합니다.
2. 플래시 통화 요청
서버에서 플래시 통화 확인을 요청합니다.
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. API 응답
API는 예상되는 발신자 ID 패턴을 반환합니다.
{
"messagelistId": 123456,
"messages": [
{
"messageId": "abc123def456",
"status": "accepted",
"callerId": "+380123456789",
"pattern": "***456789",
"to": "+380XXXXXXXXX"
}
]
}
4. 통화 시작
시스템은 사용자의 전화로 통화를 시작하고 벨이 1~2회 울린 후 종료됩니다.
5. 발신자 ID 캡처
사용자의 앱은 수신 전화의 발신자 ID를 캡처합니다.
// Android example
val cursor = contentResolver.query(
CallLog.Calls.CONTENT_URI,
arrayOf(CallLog.Calls.NUMBER),
null, null,
CallLog.Calls.DATE + " DESC"
)
6. 패턴 확인
캡처된 발신자 ID를 예상 패턴과 비교합니다.
// 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);
}
구현 예
안드로이드
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
}
}
웹(서버측)
// 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' });
}
});
응답 형식
성공 응답
{
"messagelistId": 123456,
"messages": [
{
"messageId": "abc123def456",
"status": "accepted",
"callerId": "+380123456789",
"pattern": "***456789",
"to": "+380XXXXXXXXX",
"ttl": 60
}
]
}
응답 필드
| 필드 | 유형 | 설명 |
|---|---|---|
메시지 ID |
문자열 | 고유 확인 ID |
상태 |
문자열 | 상태: '승인됨', '거부됨' |
발신자 ID |
문자열 | 전체 발신자 ID 번호 |
| '패턴' | 문자열 | 일치시킬 패턴(숫자 + 별표) |
| '에' | 문자열 | 수신자 전화번호 |
ttl |
정수 | 유효 기간(초) |
패턴 매칭
API는 일부 숫자를 마스킹하는 별표가 있는 패턴을 반환합니다.
앱은 다음을 수행해야 합니다.
- 수신 발신자 ID 캡처
- 발신자 ID에서 숫자 추출
- 패턴과 일치(별표 = 모든 숫자)
- TTL 기간 내 매칭 확인
SMS로 대체
Flash Call이 실패하면 자동으로 SMS로 대체됩니다.
{
"from": "YourApp",
"to": "+380XXXXXXXXX",
"type": "flashcall",
"messageData": {
"callerId": "+380123456789"
},
"fallback": {
"type": "sms",
"text": "Your verification code is: 123456"
},
"ttl": 60
}
사용 사례
계정 등록
SMS 비용 없이 가입 시 전화번호를 확인하세요.
로그인 인증
플래시콜을 이용한 2단계 인증.
전화번호 업데이트
사용자가 프로필을 업데이트하면 새 전화번호를 확인하세요.
거래 확인
플래시콜로 고액 거래를 확인하세요.
모범 사례
TTL
- ✅ TTL을 60-90초로 설정하세요
- ✅ 만료 후 사용자가 재시도하도록 허용
- ❌ TTL을 120초 이상 사용하지 마세요.
사용자 경험
- "통화 대기 중..." 메시지 표시
- 카운트다운 타이머 표시(60초)
- "대신 SMS 사용" 옵션 제공
- 발신자 ID 자동 감지 및 확인
오류 처리
- 누락된 전화 권한 처리
- TTL 만료 후 시간 초과
- SMS 대체 옵션 제공
- 명확한 오류 메시지 표시
권한
플래시 통화 전에 전화 권한을 요청하세요.
안드로이드:
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
iOS:
테스트
- 다양한 기기에서 테스트
- 다양한 통신사로 테스트
- 테스트 권한 거부 시나리오
- 네트워크 시간 초과 시나리오 테스트
제한사항
플랫폼 지원
- 모든 모바일 장치에서 작동
- 전화 통화 기능이 필요합니다.
- READ_PHONE_STATE 권한이 필요합니다.
- 휴대폰이 없으면 태블릿에서는 작동하지 않을 수 있습니다.
네트워크
- 활성 전화 연결이 필요합니다
- 네트워크 상태가 좋지 않을 경우 실패할 수 있음
- 통신사 제한이 적용될 수 있습니다.
- 국제 요금은 다를 수 있습니다.
개인정보 보호
- 사용자는 알 수 없는 번호를 차단할 수 있습니다.
- 일부 장치에는 통화 차단 기능이 있습니다.
- 명시적인 권한이 필요합니다.
- 사용자 개인 정보 보호 문제를 고려하십시오.
문제 해결
전화를 받지 못했습니다
- 전화기에 신호가 있는지 확인하세요.
- 숫자 형식 확인(E.164)
- 통신사 제한사항을 확인하세요
- SMS 대체를 사용해 보세요.
패턴이 일치하지 않음
- 정확한 발신자 ID 캡처 확인
- 숫자가 아닌 문자 제거
- 패턴 형식 확인
- TTL 기간 내 검증
권한이 거부되었습니다
- 권한을 올바르게 요청하세요.
- 권한이 필요한 이유를 설명하세요.
- 대안 제공(SMS)
- 우아하게 다루기