Live /call uses a Telegram Mini App plus the local realtime-gateway. It is not a native Telegram voice call.
Do not start here. Get the base Telegram bridge working first, then come back to this page.
Current implementation:
- launch surface: Telegram Mini App
- realtime backend: OpenAI Realtime
- follow-up: a structured handoff artifact returned to the bound Codex thread when the call ends
Future backend abstraction is out of scope for this repo campaign. The public docs should describe the current OpenAI-backed behavior directly.
sequenceDiagram
participant Operator as Local operator
participant Telegram as Telegram chat
participant MiniApp as Telegram Mini App
participant Gateway as realtime-gateway
participant OpenAI as OpenAI Realtime
participant Codex as Bound Codex thread
Operator->>Gateway: npm run start:gateway
Operator->>Gateway: npm run bridge:ctl -- call arm
Telegram->>MiniApp: /call
MiniApp->>Gateway: launch token + Telegram init data
Gateway->>OpenAI: create realtime session
MiniApp->>OpenAI: live browser audio
Gateway->>Codex: append transcript + handoff artifact
The important user-facing point is that /call still talks to the same bound Codex workflow. It just does it through a live Mini App plus OpenAI Realtime instead of normal Telegram messages.
These environment variables are required for live calling:
TELEGRAM_BOT_TOKENOPENAI_API_KEYREALTIME_CONTROL_SECRET
Optional for non-calling media features:
ELEVENLABS_API_KEYGOOGLE_GENAI_API_KEY
At minimum, configure these keys in bridge.config.toml:
realtime.enabled = truerealtime.bridge_idrealtime.tunnel_moderealtime.gateway_hostrealtime.gateway_portrealtime.modelrealtime.transcription_modelrealtime.voicerealtime.max_call_msrealtime.max_daily_call_ms
Important operational keys:
realtime.public_urlrealtime.control_urlrealtime.launch_token_ttl_msrealtime.bootstrap_rate_limit_per_iprealtime.bootstrap_rate_limit_per_bridgerealtime.bootstrap_rate_limit_per_user
Use realtime.tunnel_mode = "managed-quick-cloudflared" when you want the bridge to bring up a short-lived public origin automatically. In this mode:
- keep
realtime.public_url = "" - keep
realtime.control_url = ""unless you deliberately override the local bridge-control path - make sure
cloudflaredis installed and reachable asrealtime.tunnel_bin
Use realtime.tunnel_mode = "static-public-url" when you already operate a stable public origin. In this mode:
- set
realtime.public_urlto the externally reachable Mini App origin - optionally set
realtime.control_urlif the bridge control websocket should not use the default local path - make sure the public origin can reach the realtime gateway cleanly
Before you start, npm run bridge:capabilities should already show:
TELEGRAM_BOT_TOKEN: presentAuthorized chat: ...Telegram daemon: runningDesktop thread binding: readywhen usingshared-thread-resumeOPENAI_API_KEY: presentREALTIME_CONTROL_SECRET: present
- Start the realtime gateway.
npm run start:gateway- Confirm bridge status.
npm run bridge:ctl -- status
npm run bridge:capabilities- Arm the call surface.
npm run bridge:ctl -- call arm
npm run bridge:ctl -- call status- Launch the call from Telegram with
/call. - End it from Telegram with
/hangupor from the operator side with:
npm run bridge:ctl -- call hangup/call is only ready when all of these are true:
realtime.enabled = true- the realtime gateway is healthy
- the local bridge control channel is connected
- the call surface is armed
- the public Mini App origin is reachable
- Telegram currently owns the session
- a desktop thread is bound
- the call budget still has time remaining
If one of those checks fails, bridge:capabilities, /capabilities, and /call status are the fastest ways to see why.
The public config exposes explicit call caps:
realtime.max_call_msrealtime.max_daily_call_msrealtime.idle_warning_msrealtime.idle_timeout_msrealtime.auto_disarm_idle_ms
Use them aggressively. Live calling is the most network-exposed and potentially expensive feature in the repo.
- public Mini App origin unreachable
- bridge control channel not connected
- missing
REALTIME_CONTROL_SECRET - missing
OPENAI_API_KEY - stale or expired launch token
- Telegram user failed Mini App auth verification
- bootstrap rate limits exceeded
- another live call is already active or being prepared
- call budget exhausted
These routes exist to support the current implementation, but they are not a stable external API contract:
GET /healthzGET /miniappPOST /api/call/bootstrapPOST /api/call/hangupPOST /api/call/finalizeWS /ws/callWS /ws/bridge