콘텐츠로 이동

플래시콜

플래시콜은 SMS 대신 부재중 전화를 이용해 전화번호를 인증하는 전화인증 방식이다. 더 빠르고 안전하며 비용 효율적입니다.

개요

플래시 통화 확인은 다음과 같이 작동합니다.

  1. 사용자가 확인을 요청합니다.
  2. 시스템이 사용자의 전화로 통화를 시작합니다.
  3. 벨이 1~2회 울린 후 자동으로 통화가 종료됩니다.
  4. 사용자의 앱이 발신자 ID를 캡처합니다.
  5. 발신자 ID는 예상 패턴에 대해 확인됩니다.
  6. 사용자가 인증되었습니다

혜택

비용 효율적

  • 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. 사용자가 전화번호를 입력합니다.

사용자는 앱에 자신의 전화번호를 제공합니다.

Phone: +380XXXXXXXXX

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는 일부 숫자를 마스킹하는 별표가 있는 패턴을 반환합니다.

Full number: +380123456789
Pattern:     ***456789

앱은 다음을 수행해야 합니다.

  1. 수신 발신자 ID 캡처
  2. 발신자 ID에서 숫자 추출
  3. 패턴과 일치(별표 = 모든 숫자)
  4. 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:

<key>NSPhoneCallUsageDescription</key>
<string>We need phone access to verify your number</string>

테스트

  • 다양한 기기에서 테스트
  • 다양한 통신사로 테스트
  • 테스트 권한 거부 시나리오
  • 네트워크 시간 초과 시나리오 테스트

제한사항

플랫폼 지원

  • 모든 모바일 장치에서 작동
  • 전화 통화 기능이 필요합니다.
  • READ_PHONE_STATE 권한이 필요합니다.
  • 휴대폰이 없으면 태블릿에서는 작동하지 않을 수 있습니다.

네트워크

  • 활성 전화 연결이 필요합니다
  • 네트워크 상태가 좋지 않을 경우 실패할 수 있음
  • 통신사 제한이 적용될 수 있습니다.
  • 국제 요금은 다를 수 있습니다.

개인정보 보호

  • 사용자는 알 수 없는 번호를 차단할 수 있습니다.
  • 일부 장치에는 통화 차단 기능이 있습니다.
  • 명시적인 권한이 필요합니다.
  • 사용자 개인 정보 보호 문제를 고려하십시오.

문제 해결

전화를 받지 못했습니다

  • 전화기에 신호가 있는지 확인하세요.
  • 숫자 형식 확인(E.164)
  • 통신사 제한사항을 확인하세요
  • SMS 대체를 사용해 보세요.

패턴이 일치하지 않음

  • 정확한 발신자 ID 캡처 확인
  • 숫자가 아닌 문자 제거
  • 패턴 형식 확인
  • TTL 기간 내 검증

권한이 거부되었습니다

  • 권한을 올바르게 요청하세요.
  • 권한이 필요한 이유를 설명하세요.
  • 대안 제공(SMS)
  • 우아하게 다루기

다음 단계