An end-to-end overview of phone registration, Telegram requests, Android SMS capture, backend matching, private OTP delivery, and session expiration.
flowchart TB
%% =====================================================
%% 01 — SETUP & PHONE REGISTRATION
%% =====================================================
subgraph S1["01 · Setup & Phone Registration"]
direction TB
ADMIN["System Admin"]
CONFIG["Configure Android Receiver<br/>1–2 phone numbers"]
READY{"Receiver ready?"}
READINESS["SMS permissions<br/>Background execution<br/>JobScheduler readiness"]
TEST["TEST AND SEND<br/>POST /api/test"]
PHONE_STORE[("Phone Store<br/>Registered phones<br/>Usage counters")]
ADMIN --> CONFIG
CONFIG --> READY
READY -- "No" --> READINESS
READINESS --> READY
READY -- "Yes" --> TEST
TEST --> PHONE_STORE
end
%% =====================================================
%% 02 — TELEGRAM REQUEST & SESSION CREATION
%% =====================================================
subgraph S2["02 · Telegram Request & Session Creation"]
direction TB
USER["Requester"]
START["Start bot privately<br/>One-time prerequisite"]
CODE["Send /code 09xxxxxxxxx<br/>in active Telegram group"]
WEBHOOK["Telegram Webhook<br/>POST /telegram/webhook"]
VALIDATE["Validate group, mode,<br/>command and phone"]
REGISTERED{"Phone registered?"}
REJECT_PHONE["Reject request<br/>Phone not registered"]
ACTIVE{"Active session<br/>already exists?"}
REJECT_DUP["Reject duplicate request<br/>Existing session preserved"]
PRIVATE_READY["Send private<br/>Session Ready message"]
PRIVATE_OK{"Private message sent?"}
START_REQUIRED["Ask requester to<br/>start the bot first"]
CREATE["Create active session"]
SESSION_STORE[("Session Store<br/>Requester · Phone · Session ID<br/>CreatedAt · ExpiresAt")]
TIMER["Register 120-second<br/>expiration timer"]
GROUP_ACCEPT["Reply in group<br/>Request accepted"]
USER --> START
START --> CODE
CODE --> WEBHOOK
WEBHOOK --> VALIDATE
VALIDATE --> REGISTERED
REGISTERED -- "No" --> REJECT_PHONE
REGISTERED -- "Yes" --> ACTIVE
ACTIVE -- "Yes" --> REJECT_DUP
ACTIVE -- "No" --> PRIVATE_READY
PRIVATE_READY --> PRIVATE_OK
PRIVATE_OK -- "No" --> START_REQUIRED
PRIVATE_OK -- "Yes" --> CREATE
CREATE --> SESSION_STORE
CREATE --> TIMER
CREATE --> GROUP_ACCEPT
end
%% Registered phones are used by the request validation path
PHONE_STORE --> REGISTERED
%% =====================================================
%% 03 — DIVAR & ANDROID SMS CAPTURE
%% =====================================================
subgraph S3["03 · Divar & Android SMS Capture"]
direction TB
DIVAR_INPUT["Requester enters the same<br/>phone number in Divar"]
DIVAR["Divar requests verification"]
SMS["Verification SMS arrives"]
RECEIVER["Android SMS BroadcastReceiver"]
RECOVERY["Recent SMS Recovery<br/>Missed-broadcast fallback"]
PARSE{"Valid Divar SMS<br/>with 6-digit OTP?"}
IGNORE["Ignore message"]
EXTRACT["Normalize digits<br/>Extract 6-digit OTP"]
CANDIDATES["Resolve SIM metadata<br/>Build candidate phones"]
DEDUP{"Fingerprint already seen<br/>within 24 hours?"}
DISCARD["Discard duplicate"]
QUEUE[("Persistent Local Queue<br/>FIFO · PendingDelivery<br/>60-second TTL")]
JOB["JobScheduler<br/>Network required"]
SUBMIT["Submit OTP + candidate phones<br/>POST /api/sms"]
DIVAR_INPUT --> DIVAR
DIVAR --> SMS
SMS --> RECEIVER
RECEIVER --> PARSE
RECOVERY -. "fallback" .-> PARSE
PARSE -- "No" --> IGNORE
PARSE -- "Yes" --> EXTRACT
EXTRACT --> CANDIDATES
CANDIDATES --> DEDUP
DEDUP -- "Yes" --> DISCARD
DEDUP -- "No" --> QUEUE
QUEUE --> JOB
JOB --> SUBMIT
end
GROUP_ACCEPT --> DIVAR_INPUT
%% =====================================================
%% 04 — BACKEND MATCHING & SECURE DELIVERY
%% =====================================================
subgraph S4["04 · Backend Matching & Secure Delivery"]
direction TB
AUTH["Authenticate Android request<br/>App Secret + App Mode"]
AUTH_OK{"Request authenticated?"}
API_REJECT["Reject API request<br/>Queue item remains pending"]
CLEAN["Prune expired sessions"]
MATCH["Match candidate phones<br/>against active sessions"]
MATCH_COUNT{"Active session matches"}
NO_SESSION["0 matches<br/>Accepted · Not delivered"]
AMBIGUOUS["Multiple matches<br/>Ambiguous · Sessions preserved"]
ONE_MATCH["Exactly one match<br/>Resolve original requester"]
PRIVATE_OTP["Send OTP privately<br/>through Telegram"]
DELIVERY_OK{"Private delivery successful?"}
RETRY["HTTP 502<br/>Keep session + queue item"]
CLOSE["Close active session<br/>Cancel expiration timer"]
COUNTER["Increment successfulDeliveries"]
AUDIT[("Audit History<br/>Delivery / session events")]
GROUP_SUCCESS["Reply to original group message<br/>Code received and sent<br/>OTP never appears in group"]
DONE(["OTP Delivered"])
AUTH --> AUTH_OK
AUTH_OK -- "No" --> API_REJECT
AUTH_OK -- "Yes" --> CLEAN
CLEAN --> MATCH
MATCH --> MATCH_COUNT
MATCH_COUNT -- "0" --> NO_SESSION
MATCH_COUNT -- "Multiple" --> AMBIGUOUS
MATCH_COUNT -- "Exactly 1" --> ONE_MATCH
ONE_MATCH --> PRIVATE_OTP
PRIVATE_OTP --> DELIVERY_OK
DELIVERY_OK -- "No" --> RETRY
DELIVERY_OK -- "Yes" --> CLOSE
CLOSE --> COUNTER
CLOSE --> AUDIT
CLOSE --> GROUP_SUCCESS
GROUP_SUCCESS --> DONE
end
SUBMIT --> AUTH
SESSION_STORE --> MATCH
API_REJECT -. "retry while item is fresh" .-> JOB
RETRY -. "exponential backoff" .-> JOB
NO_SESSION -->|"200 · accepted"| DEQUEUE1["Remove queue item"]
AMBIGUOUS -->|"200 · accepted"| DEQUEUE2["Remove queue item"]
DONE -->|"200 · delivered"| DEQUEUE3["Remove queue item"]
%% =====================================================
%% 05 — SESSION EXPIRATION
%% =====================================================
subgraph S5["05 · Session Expiration"]
direction TB
DEADLINE{"Session still active<br/>after 120 seconds?"}
TIMER_END["No action<br/>Session already completed"]
EXPIRE["Expire session"]
EXPIRE_AUDIT["Record expiration event"]
NOTIFY_PRIVATE["Notify requester privately"]
NOTIFY_GROUP["Reply Session expired<br/>in Telegram group"]
EXPIRED(["Session Expired"])
DEADLINE -- "No" --> TIMER_END
DEADLINE -- "Yes" --> EXPIRE
EXPIRE --> EXPIRE_AUDIT
EXPIRE --> NOTIFY_PRIVATE
EXPIRE --> NOTIFY_GROUP
NOTIFY_PRIVATE --> EXPIRED
NOTIFY_GROUP --> EXPIRED
end
TIMER -. "120s deadline" .-> DEADLINE
%% =====================================================
%% STYLES
%% =====================================================
classDef actor fill:#f8fafc,stroke:#64748b,color:#0f172a,stroke-width:1.5px;
classDef telegram fill:#eff6ff,stroke:#2563eb,color:#172554,stroke-width:1.5px;
classDef backend fill:#f5f3ff,stroke:#7c3aed,color:#2e1065,stroke-width:1.5px;
classDef android fill:#f0fdf4,stroke:#16a34a,color:#14532d,stroke-width:1.5px;
classDef external fill:#fff7ed,stroke:#ea580c,color:#7c2d12,stroke-width:1.5px;
classDef store fill:#fffbeb,stroke:#d97706,color:#78350f,stroke-width:1.8px;
classDef decision fill:#ffffff,stroke:#475569,color:#0f172a,stroke-width:1.8px;
classDef warning fill:#fff7ed,stroke:#ea580c,color:#7c2d12,stroke-width:1.5px;
classDef failure fill:#fef2f2,stroke:#dc2626,color:#7f1d1d,stroke-width:1.5px;
classDef success fill:#ecfdf5,stroke:#059669,color:#064e3b,stroke-width:2px;
class ADMIN,USER actor;
class START,CODE,WEBHOOK,PRIVATE_READY,GROUP_ACCEPT,PRIVATE_OTP,GROUP_SUCCESS,NOTIFY_PRIVATE,NOTIFY_GROUP telegram;
class VALIDATE,CREATE,TIMER,AUTH,CLEAN,MATCH,ONE_MATCH,CLOSE,COUNTER,EXPIRE,EXPIRE_AUDIT backend;
class CONFIG,READINESS,TEST,RECEIVER,RECOVERY,EXTRACT,CANDIDATES,JOB,SUBMIT android;
class DIVAR_INPUT,DIVAR,SMS external;
class PHONE_STORE,SESSION_STORE,QUEUE,AUDIT store;
class READY,REGISTERED,ACTIVE,PRIVATE_OK,PARSE,DEDUP,AUTH_OK,MATCH_COUNT,DELIVERY_OK,DEADLINE decision;
class REJECT_PHONE,REJECT_DUP,START_REQUIRED,NO_SESSION,AMBIGUOUS warning;
class IGNORE,DISCARD,API_REJECT,RETRY failure;
class DONE,EXPIRED success;