Към съдържанието

Flash Call (Тихо обаждане)

Flash Call е метод за потвърждение на телефонен номер, който използва пропуснато обаждане вместо SMS. Това е по-бързо, по-сигурно и по-ефективно решение за бизнес клиенти.

Преглед

Удостоверяването чрез Flash Call работи по следния начин:

  1. Потребителят заявява проверка (verification)
  2. Системата инициира обаждане към телефона на потребителя
  3. Обаждането се прекратява автоматично след 1-2 позвънявания
  4. Приложението на потребителя улавя ID на обаждащия се (секретния код)
  5. Идентификаторът на обаждащия се се проверява спрямо очаквания шаблон
  6. Потребителят е удостоверен

Предимства

Рентабилен (Cost-Effective)

  • До 10 пъти по-евтино от SMS
  • Без такси за доставка на съобщения
  • Намалени разходи за удостоверяване при голям обем

По-бързо

  • Мигновена проверка (1-3 секунди)
  • Без чакане за получаване на SMS
  • По-добро потребителско изживяване

По-сигурно

  • По-трудно за прехващане от SMS
  • Няма OTP (напр. кодировка), видим в известията
  • Устойчив на атаки тип "Смяна на SIM карта" (SIM swap)

Глобален обхват

  • Работи в страни с ограничения за SMS
  • Няма проблеми с филтрирането на SMS-и
  • Универсална телефонна съвместимост

Използване на Flash Call

Заявка

{
  "from": "YourApp",
  "to": "+380XXXXXXXXX",
  "type": "flashcall",
  "messageData": {
    "callerId": "+380123456789"
  }
}

Параметри

Параметър Тип Задължителен Описание
from string Да Вашият идентификатор на подател
to string Да Телефонен номер на получателя (E.164)
type string Да Задайте като "flashcall"
callerId string Да Телефонен номер, който ще се обади на потребителя
ttl integer Не Време на живот (Time-to-live) в секунди (по подразбиране: 60)

Как работи

1. Потребителят въвежда телефонен номер

Потребителят предоставя телефонния си номер във вашето приложение:

Телефон: +380XXXXXXXXX

2. Изискване на Flash Call

Вашият сървър заявява потвърждение чрез Flash Call:

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-то връща очаквания модел (pattern) за ID на обаждащия се:

{
  "messagelistId": 123456,
  "messages": [
    {
      "messageId": "abc123def456",
      "status": "accepted",
      "callerId": "+380123456789",
      "pattern": "***456789",
      "to": "+380XXXXXXXXX"
    }
  ]
}

4. Започнете обаждането

Системата инициира обаждане към телефона на потребителя и го прекратява след 1-2 позвънявания.

5. Уловете ID (номера) на обаждащия се

Приложението на потребителя улавя ID-то на входящото обаждане:

// Android пример
val cursor = contentResolver.query(
    CallLog.Calls.CONTENT_URI,
    arrayOf(CallLog.Calls.NUMBER),
    null, null,
    CallLog.Calls.DATE + " DESC"
)

6. Проверка на модела (pattern)

Сравнете заснетия ID с очаквания модел:

// JavaScript пример
function verifyFlashCall(callerId, pattern) {
  // Премахнете символи, които не са цифри
  const callerDigits = callerId.replace(/\D/g, '');
  const patternDigits = pattern.replace(/\*/g, '.');

  // Проверете дали съвпада с шаблона
  const regex = new RegExp(patternDigits);
  return regex.test(callerDigits);
}

Примери за внедряване

Android

class FlashCallVerification {
    fun requestFlashCall(phoneNumber: String) {
        // 1. Изискайте flash call от API
        val response = api.requestFlashCall(phoneNumber)
        val pattern = response.pattern

        // 2. Изчакайте входящо повикване
        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. Проверка на ID спрямо шаблона
                        if (verifyPattern(callerId, pattern)) {
                            onVerificationSuccess()
                        }
                    }
                }
            }
        }

        // Регистриране на приемник (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. Заявете flash call от API
        api.requestFlashCall(phoneNumber) { response in
            let pattern = response.pattern

            // 2. Използвайте CallKit, за да откриете входящо повикване
            let provider = CXProvider(configuration: providerConfiguration)
            provider.setDelegate(self, queue: nil)

            // Запазване на шаблон за проверка
            self.expectedPattern = pattern
        }
    }

    // Делегат (Delegate) на CallKit
    func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
        // Уловете Caller ID
        let callerId = action.callUUID.uuidString

        // Проверете спрямо шаблона
        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 (Server-Side)

// Node.js пример
const express = require('express');
const app = express();

app.post('/request-verification', async (req, res) => {
  const { phoneNumber } = req.body;

  // 1. Поискайте 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. Съхраняване на модела (pattern) за проверка на Redis
  await redis.setex(`flashcall:${messageId}`, 60, pattern);

  // 3. Връщане на шаблона към клиента
  res.json({ messageId, pattern });
});

app.post('/verify-flashcall', async (req, res) => {
  const { messageId, callerId } = req.body;

  // 1. Вземете очаквания шаблон
  const pattern = await redis.get(`flashcall:${messageId}`);

  if (!pattern) {
    return res.status(400).json({ error: 'Проверката е изтекла' });
  }

  // 2. Проверете идентификатора на повикващия
  const regex = new RegExp(pattern.replace(/\*/g, '\\d'));
  const isValid = regex.test(callerId);

  if (isValid) {
    // Маркирайте телефона като проверен
    await markPhoneVerified(callerId);
    res.json({ verified: true });
  } else {
    res.status(400).json({ error: 'Невалиден номер на повикващия' });
  }
});

Формат на отговора (Response Format)

Успешен отговор

{
  "messagelistId": 123456,
  "messages": [
    {
      "messageId": "abc123def456",
      "status": "accepted",
      "callerId": "+380123456789",
      "pattern": "***456789",
      "to": "+380XXXXXXXXX",
      "ttl": 60
    }
  ]
}

Полета за отговор

Поле Тип Описание
messageId string Уникален идентификатор (ID) за проверка
status string Статус: accepted, rejected
callerId string Пълен номер за идентификация на повикващия
pattern string Модел, който трябва да съответства (цифри + звездички)
to string Телефонен номер на получателя
ttl integer Период на валидност в секунди

Съвпадение на шаблона (Pattern Matching)

API връща модел със звездички, маскиращи някои цифри:

Пълен номер: +380123456789
Модел (Pattern):     ***456789

Вашето приложение трябва да:

  1. Заснема ID на входящия повикващ
  2. Извлича цифри от ID-то
  3. Съвпада спрямо шаблона (звездички = всяка цифра)
  4. Потвърди съответствието в рамките на TTL периода

Резервен вариант към SMS (Fallback to SMS)

Ако Flash Call е неуспешно, автоматично преминава към SMS:

{
  "from": "YourApp",
  "to": "+380XXXXXXXXX",
  "type": "flashcall",
  "messageData": {
    "callerId": "+380123456789"
  },
  "fallback": {
    "type": "sms",
    "text": "Вашият код за потвърждение е: 123456"
  },
  "ttl": 60
}

Случаи на употреба (Use Cases)

Регистрация на профил

Проверявайте телефонни номера по време на регистрация без разходи за SMS.

Проверка за влизане (Login)

Двуфакторно удостоверяване чрез използване на Flash Call.

Актуализиране на телефонен номер

Проверете новия телефонен номер, когато потребителят актуализира потребителския си профил.

Потвърждение на транзакция

Потвърдете транзакции с висока стойност с flash call.

Добри практики

TTL

  • ✅ Задайте TTL на 60-90 секунди
  • ✅ Позволете на потребителя да опита отново след изтичане на срока
  • ❌ Не използвайте TTL по-дълъг от 120 секунди

Потребителско изживяване (UX)

  • Показване на съобщение „Изчаква се повикване...“
  • Показване на таймер за обратно броене (60 секунди)
  • Осигурете опция "Вместо това използвай SMS"
  • Автоматично откриване и проверка на идентификатора на обаждащия се

Обработка на грешки

  • Справяне с липсващи разрешения за телефон
  • Изтичане на таймаут след изтичане на TTL
  • Предоставяне на опция за резервен SMS (fallback)
  • Показване на ясни съобщения за грешка

Разрешения (Permissions)

Поискайте разрешения за телефон (за Android) преди flash call:

Android:

<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>

Тестване

  • Тествайте на различни устройства
  • Тествайте с различни оператори
  • Тестови сценарии за отказ на разрешение
  • Тестови сценарии за изчакване (timeout) на мрежата

Ограничения

Поддръжка в платформата

  • Работи на всички мобилни устройства
  • Изисква възможност за телефонно обаждане
  • Нуждае се от разрешение (напр. READ_PHONE_STATE)
  • Може да не работи на таблети без приложение за Телефон

Мрежа

  • Изисква активна телефонна връзка
  • Може да се провали при лоши мрежови условия
  • Може да се прилагат ограничения на оператора
  • Международните тарифи може да варират

Поверителност (Privacy)

  • Потребителите могат да блокират неизвестни номера
  • Някои устройства имат блокиране на обажданията
  • Изисква изрични разрешения
  • Вземете предвид опасенията за поверителността на потребителите

Отстраняване на проблеми

Обаждане Не е Получено

  • Проверете дали телефонът има сигнал
  • Проверете формата на номера (E.164)
  • Проверете ограниченията на оператора
  • Опитайте резервен SMS (fallback)

Шаблонът Не Съвпада

  • Уверете се, че улавяте (capturing) правилен номер на повикващия
  • Премахнете символите, които не са цифри
  • Проверете формата на шаблона
  • Проверете периода TTL

Разрешение Отказано (Permission Denied)

  • Поискайте разрешения правилно
  • Обяснете защо са необходими разрешения
  • Осигурете алтернатива (SMS)
  • Справете се грациозно