Dashboard Call
An operator posts a number and a mission. Same rules as an assistant.
HelloLoop
Every hop as a flowchart. The website talks in forms. The phone room talks in voice. They never share a process.
Cloud calling is two machines talking about the same ticket. The website never hears audio. The gateway is an always-on phone room; it never owns the wallet or who can see whose data. Gemini Live sits only on the voice side.
Who can start
An operator posts a number and a mission. Same rules as an assistant.
A minute clock next to the phone room. Claims one lead, then dials.
Places a call through the same door. Wallet and DNC still run.
HTTPS forms
Control plane: the website. No sound.
Checks, route, ticket, ask the carrier to ring.
Signed stream address after the lead picks up.
Gateway fetches the mission and the assigned agent.
Status, duration, charge once.
records · carrier dial · signed stream
State: one workspace per customer. Rows stay inside that workspace.
Balance checked before dial. Charged after hangup.
One row per attempt. Status only moves forward.
What operators read. Same place for inbound, outbound, campaigns.
LIVE VOICE
Media plane: not the website. Always-on gateway.
Rings the lead from your number. Streams voice to the gateway.
Checks the ticket is real, bridges voice, writes the recording.
Talks, listens, lets the lead interrupt, calls tools.
phone ring
Hears the agent. Speaks into the carrier. Never talks to the website.
Charged once per ticket. A second hangup cannot bill twice.
Three starters, one place-call door, one ticket, one voice path. This page is cloud calling only.
flowchart TD
UI["Dashboard Call"] --> PLACE["Place call"]
CAMP["Campaign pump"] --> PLACE
MCP["Assistant"] --> PLACE
PLACE --> GATES["DNC · wallet · slot"]
GATES --> TICKET["Create call ticket"]
TICKET --> KEY["Start voice session"]
KEY --> DIAL["Ask carrier to ring"]
DIAL --> RING["Mobile rings"]
RING --> XML["Signed stream recipe"]
XML --> GW["Gateway checks ticket"]
GW --> BOOT["Load mission and agent"]
BOOT --> GEM["Gemini Live"]
GEM --> TALK["Voice both ways"]
TALK --> HANG["Hangup"]
HANG --> BILL["Charge once"]
BILL --> MP3["Save recording"]
BILL --> LOG["Write history"]
classDef office fill:#e8f4fb,stroke:#3d8fb5,color:#1c1712
classDef gate fill:#dcf5ea,stroke:#2a9d6e,color:#1c1712
classDef gem fill:#ece4f8,stroke:#8b6cc9,color:#1c1712
classDef money fill:#fff3d1,stroke:#c9a227,color:#1c1712
class UI,CAMP,MCP,PLACE,GATES,TICKET,KEY,DIAL,XML,BOOT office
class GW,TALK,MP3 gate
class GEM gem
class BILL,LOG money
Dashboard, assistants, and the campaign clock share one door. A fail at any diamond never rings the carrier.
flowchart TD
IN["Number plus mission"] --> AUTH{"Signed in?"}
AUTH -->|no| STOP1["Stop"]
AUTH -->|yes| EMER{"Emergency number?"}
EMER -->|yes| STOP2["Blocked"]
EMER -->|no| NORM["Normalize the number"]
NORM --> DNC{"On the DNC list?"}
DNC -->|yes| STOP3["Do not call"]
DNC -->|no| ROUTE["Pick a carrier"]
ROUTE --> WAL{"Wallet covers 1 min?"}
WAL -->|no| STOP4["Ask to top up"]
WAL -->|yes| SLOT{"Under the call cap?"}
SLOT -->|no| WAIT["Try again shortly"]
SLOT -->|yes| DID["Need an outbound number"]
DID --> ROW["Create ticket"]
ROW --> LOG["Open history row"]
LOG --> REST["Carrier rings"]
REST --> OK["Call started"]
REST --> FAIL["Failed. Free the slot"]
classDef stop fill:#f8d7d3,stroke:#c0392b,color:#1c1712
classDef office fill:#e8f4fb,stroke:#3d8fb5,color:#1c1712
classDef money fill:#fff3d1,stroke:#c9a227,color:#1c1712
class STOP1,STOP2,STOP3,STOP4,WAIT,FAIL stop
class IN,AUTH,NORM,ROUTE,DID,ROW,LOG,REST,OK office
class WAL money
Voice credentials never go to the browser. The desk can read the ticket. It cannot read the key.
Read left to right, then down. After the stream is up, the website only handles hangup and tools. Voice stays carrier to gateway to Gemini.
Until the phone rings
sequenceDiagram
actor You
participant Web as Website
participant P as Carrier
actor Lead
You->>Web: Call this number
Web->>Web: DNC, route, wallet
Web->>P: Please ring them
P->>Lead: Phone rings
Lead->>P: Answer
P->>Web: They picked up
Web-->>P: Signed stream address
After they pick up
sequenceDiagram
participant P as Carrier
actor Lead
participant G as Gateway
participant Gem as Gemini
participant Web as Website
P->>G: Open the voice stream
G->>Web: Load this ticket
Web-->>G: Mission and agent
G->>Gem: Open Live
loop Talk
Lead->>P: Speak
P->>G: Voice frames
G->>Gem: Hear the lead
Gem->>G: Hear the agent
G->>P: Play the agent
P->>Lead: Hear the agent
end
Lead->>P: Hangup
P->>Web: Call ended
G->>Web: Session and recording
Web->>Web: Charge once
The answer address is not a public audio socket. The carrier must prove the request. The recipe then points at the gateway with a short-lived signed ticket. Guessing the link fails.
flowchart TD
ANS["Lead answers"] --> POST["Carrier asks for recipe"]
POST --> SIG{"Request proven?"}
SIG -->|no| REJ["Refuse"]
SIG -->|yes| URL["Build stream address"]
URL --> TOK["Sign the ticket"]
TOK --> XML["Return the recipe"]
XML --> OPEN["Carrier opens the stream"]
OPEN --> GW{"Ticket matches?"}
GW -->|no| CLOSE["Close the socket"]
GW -->|yes| BOOT["Load the call"]
BOOT --> PERS["Pick the agent"]
PERS --> LIVE["Open Gemini Live"]
classDef office fill:#e8f4fb,stroke:#3d8fb5,color:#1c1712
classDef gate fill:#dcf5ea,stroke:#2a9d6e,color:#1c1712
classDef stop fill:#f8d7d3,stroke:#c0392b,color:#1c1712
classDef gem fill:#ece4f8,stroke:#8b6cc9,color:#1c1712
class ANS,POST,SIG,URL,TOK,XML,BOOT,PERS office
class OPEN,GW gate
class REJ,CLOSE stop
class LIVE gem
We pick a carrier from the destination number, not from who the customer is. This page is the cloud path.
flowchart TD
START["Place call"] --> DEST{"Domestic number?"}
DEST -->|yes| PL["Primary domestic carrier"]
PL -->|not configured| VL["Domestic fallback"]
DEST -->|no| TX["Primary international"]
TX -->|not configured| PL2["International fallback"]
PL --> STREAM["Signed stream to gateway"]
VL --> STREAM
TX --> STREAM
PL2 --> STREAM
classDef office fill:#e8f4fb,stroke:#3d8fb5,color:#1c1712
classDef carrier fill:#fde8dc,stroke:#d46a32,color:#1c1712
classDef gate fill:#dcf5ea,stroke:#2a9d6e,color:#1c1712
class START,DEST office
class PL,VL,TX,PL2 carrier
class STREAM gate
Domestic and international each have a primary and a fallback. Every path still lands on the same signed stream into the gateway.
Screens write forms. Carriers report answer and hangup. The gateway asks for the ticket and sends the recording back. One engine behind every starter.
flowchart TD
DESK["Desk"] --> PLACE["Place call"]
MCP["Assistant"] --> PLACE
CRON["Minute clock"] --> PUMP["Claim next lead"]
PUMP --> PLACE
PLACE --> CORE["Same dial engine"]
CORE --> CARRIER["Carrier rings"]
CARRIER --> XML["Answer recipe"]
CARRIER --> WH["Hangup"]
GW["Gateway"] --> BOOT["Load the ticket"]
GW --> REP["Send session back"]
XML --> GW
WH --> BILL["Charge wallet"]
REP --> CLOG["Write transcript"]
classDef office fill:#e8f4fb,stroke:#3d8fb5,color:#1c1712
classDef gate fill:#dcf5ea,stroke:#2a9d6e,color:#1c1712
classDef money fill:#fff3d1,stroke:#c9a227,color:#1c1712
class DESK,MCP,PLACE,CORE,PUMP,XML,WH,BOOT,REP office
class GW,CARRIER gate
class BILL,CLOG,CRON money
The phone room. If it is down, the website can still look fine and cloud calling is still dead. Audio never transits the website.
flowchart TD
IN["Carrier stream"] --> TOK{"Ticket real?"}
TOK -->|no| X["Close"]
TOK -->|yes| GET["Load the call"]
GET --> NEED{"Mission ready?"}
NEED -->|no| X
NEED -->|yes| LIVE["Open Gemini Live"]
LIVE --> LOOP["Voice loop"]
LOOP --> UP["Hear the lead"]
LOOP --> DOWN["Speak to the lead"]
LOOP --> BARGE{"Interrupted?"}
BARGE -->|yes| CLR["Stop playback"]
LOOP --> BUF["Record both sides"]
BUF --> FF["Save the recording"]
FF --> REP["Send session back"]
LOOP --> TOOL["Agent uses a tool"]
TOOL --> WAIT{"Website confirmed?"}
WAIT -->|yes| TELL["Agent may say it"]
WAIT -->|no| HOLD["Do not claim it"]
classDef gate fill:#dcf5ea,stroke:#2a9d6e,color:#1c1712
classDef gem fill:#ece4f8,stroke:#8b6cc9,color:#1c1712
classDef stop fill:#f8d7d3,stroke:#c0392b,color:#1c1712
classDef office fill:#e8f4fb,stroke:#3d8fb5,color:#1c1712
class IN,TOK,GET,LOOP,UP,DOWN,CLR,BUF,FF,REP gate
class LIVE,BARGE,TELL gem
class X,HOLD stop
class TOOL,WAIT,NEED office
Every carrier, one room
flowchart TD
P["Domestic carrier"] --> ROOM["Same voice room"]
T["International carrier"] --> ROOM
V["Fallback carrier"] --> ROOM
B["In-app test call"] --> ROOM
ROOM --> GEM["Gemini Live"]
Barge-in
flowchart TD
SPK["Agent speaking"] --> INT{"Lead talks?"}
INT -->|no| KEEP["Keep playing"]
INT -->|yes| GRACE{"Still greeting?"}
GRACE -->|yes| SKIP["Let the greeting finish"]
GRACE -->|no| CLR["Stop and listen"]
Gemini owns turn-taking. The gateway is a pipe plus a recorder. It does not mute the lead on its own.
sequenceDiagram
participant Lead
participant Carrier
participant Gateway
participant Gemini
Lead->>Carrier: Speak
Carrier->>Gateway: Voice in
Gateway->>Gemini: Hear the lead
Gemini-->>Gateway: Hear the agent
Gateway->>Carrier: Play the agent
Carrier->>Lead: Speaker
Gemini-->>Gateway: Interrupted
Gateway->>Carrier: Stop playback
Gemini-->>Gateway: Use a tool
Gateway->>Gateway: Ask the website
Gateway->>Gemini: Result after it lands
Carriers may report the same hangup more than once, and late. A finished call stays finished. A late “stream stopped” cannot reopen it or steal a line.
flowchart TD
H["Hangup or failed"] --> MAP["Read the outcome"]
MAP --> RANK{"Newer than current?"}
RANK -->|no| IGNORE["Ignore"]
RANK -->|yes| UPD["Update the ticket"]
UPD --> CL["Update history"]
UPD --> Q["Free the queue slot"]
UPD --> KEY["End the voice session"]
CL --> DEB["Charge the wallet"]
DEB --> ID{"Already billed?"}
ID -->|yes| SKIP["No second charge"]
ID -->|no| CENTS["Write the ledger"]
UPD --> EVT["Keep an event"]
classDef office fill:#e8f4fb,stroke:#3d8fb5,color:#1c1712
classDef money fill:#fff3d1,stroke:#c9a227,color:#1c1712
classDef stop fill:#f8d7d3,stroke:#c0392b,color:#1c1712
class H,MAP,RANK,UPD,CL,Q,KEY,EVT office
class DEB,ID,CENTS money
class IGNORE,SKIP stop
Your number is a doorbell. Same gateway as outbound. Unknown numbers, including shared demo numbers, are rejected so they cannot land on another workspace.
flowchart TD
CALL["Caller dials your number"] --> WH["Carrier reports it"]
WH --> MATCH{"Your number?"}
MATCH -->|no or demo| REJ["Reject"]
MATCH -->|yes| ORG["Your workspace and agent"]
ORG --> T["New inbound ticket"]
T --> LOG["Open history"]
LOG --> XML["Same signed stream"]
XML --> G["Gateway loads the call"]
G --> GEM["Gemini Live"]
GEM --> FIN["Hangup plus transcript"]
classDef office fill:#e8f4fb,stroke:#3d8fb5,color:#1c1712
classDef gate fill:#dcf5ea,stroke:#2a9d6e,color:#1c1712
classDef gem fill:#ece4f8,stroke:#8b6cc9,color:#1c1712
classDef stop fill:#f8d7d3,stroke:#c0392b,color:#1c1712
class CALL,WH,MATCH,ORG,T,LOG,XML office
class G,FIN gate
class GEM gem
class REJ stop
A campaign is a list plus a pace. Each tick uses the same dial path as the Call button. The clock lives next to the phone room so it keeps moving even if the website is asleep.
flowchart TD
Q["Queue waiting"] --> CRON["Minute clock"]
CRON --> PUMP["Wake the campaign"]
PUMP --> REAP["Close stuck tickets"]
PUMP --> RETRY["Wake retries"]
PUMP --> CLAIM["Claim the next lead"]
CLAIM --> OK{"Wallet and slot ok?"}
OK -->|no| WAIT["Wait one minute"]
OK -->|yes| SAME["Same cloud dial"]
SAME --> RING["One lead rings"]
RING --> Q
classDef office fill:#e8f4fb,stroke:#3d8fb5,color:#1c1712
classDef money fill:#fff3d1,stroke:#c9a227,color:#1c1712
classDef gate fill:#dcf5ea,stroke:#2a9d6e,color:#1c1712
class Q,CRON,PUMP,REAP,RETRY,CLAIM,SAME office
class OK,WAIT money
class RING gate
Start and each tick check the wallet for one minute at that destination. Empty wallet stops new dials. It does not hang up a live call.
Prepaid. We check that the next minute can be paid before anyone rings. We charge after hangup. Rates live on the pricing page, not here.
flowchart TD
ASK["Want to dial"] --> PRE{"Wallet covers 1 min?"}
PRE -->|no| STOP["Do not dial"]
PRE -->|yes| DIAL["Carrier rings"]
DIAL --> HANG["Hangup"]
HANG --> DEB["Charge the call"]
DEB --> ID{"Already billed?"}
ID -->|yes| SKIP["No second charge"]
ID -->|no| WRITE["Write the ledger"]
classDef money fill:#fff3d1,stroke:#c9a227,color:#1c1712
classDef stop fill:#f8d7d3,stroke:#c0392b,color:#1c1712
class ASK,PRE,DIAL,HANG,DEB,ID,WRITE money
class STOP,SKIP stop
Top-up credits a payment once, from the return page and from the processor webhook. Number rent is a separate cycle. An overdue number stays up so production does not go silent overnight.
Cloud is not one call worldwide. Each workspace has a cap. Stuck tickets are closed so they cannot hold a line forever.
flowchart TD
NEW["New cloud dial"] --> CNT["Count live tickets"]
CNT --> STALE{"Stuck ticket?"}
STALE -->|yes| REAP["Mark it failed"]
STALE -->|no| N["Live plus 1"]
N --> CAP{"Over the cap?"}
CAP -->|yes| BLOCK["Do not dial"]
CAP -->|no| GO["Create ticket and dial"]
classDef office fill:#e8f4fb,stroke:#3d8fb5,color:#1c1712
classDef stop fill:#f8d7d3,stroke:#c0392b,color:#1c1712
class NEW,CNT,STALE,N,CAP,GO office
class REAP,BLOCK stop
flowchart TD
ORG["Workspace"] --> SET["Settings"]
ORG --> PER["Agents"]
ORG --> CON["Contacts"]
ORG --> DID["Your numbers"]
ORG --> DIS["Live tickets"]
ORG --> LOG["Call history"]
ORG --> WAL["Wallet ledger"]
ORG --> EVT["Call events"]
CAMP["Campaigns"] --> Q["Campaign queue"]
Q --> DIS
DIS --> LOG
DID --> DIS
DIS --> EVT
PER --> DIS
classDef office fill:#e8f4fb,stroke:#3d8fb5,color:#1c1712
classDef money fill:#fff3d1,stroke:#c9a227,color:#1c1712
class ORG,SET,PER,CON,DID,DIS,LOG,EVT,CAMP,Q office
class WAL money
The live ticket is the attempt in flight. History is what the desk reads. Events are an append-only trail. Wallet rows are never edited in place.
Claude or ChatGPT can place a call, read history, or start a campaign. Same door, same wallet, same DNC. There is no “just enqueue” shortcut.
flowchart TD
C["Assistant"] --> A{"Signed in?"}
A -->|no| STOP["Refuse"]
A -->|yes| S["Assistant tools"]
S --> T["Place a call"]
T --> R["Same DNC and wallet"]
R --> D["Same dial engine"]
D --> CAR["Carrier rings"]
S --> LIST["List call history"]
LIST --> LOG["History"]
S --> START["Start a campaign"]
START --> PLANE["Same minute clock"]
classDef office fill:#e8f4fb,stroke:#3d8fb5,color:#1c1712
classDef money fill:#fff3d1,stroke:#c9a227,color:#1c1712
classDef stop fill:#f8d7d3,stroke:#c0392b,color:#1c1712
class C,A,S,T,R,D,LIST,LOG,START,PLANE office
class CAR money
class STOP stop
Each customer is a workspace. A signed-in user only sees that workspace. The gateway loads one ticket. A carrier report only updates the matching call.
flowchart TD
USER["Signed-in user"] --> MEM["Workspace member"]
MEM --> ORG["That workspace"]
ORG --> READ["Desk reads own rows"]
GW["Gateway"] --> BOOT["Load one ticket"]
BOOT --> LIVE["Open that voice session"]
CAR["Carrier report"] --> SIG["Prove it is the carrier"]
SIG --> MATCH["Match the ticket"]
MATCH --> WRITE["Update that call only"]
classDef office fill:#e8f4fb,stroke:#3d8fb5,color:#1c1712
classDef gate fill:#dcf5ea,stroke:#2a9d6e,color:#1c1712
class USER,MEM,ORG,READ,CAR,SIG,MATCH,WRITE office
class GW,BOOT,LIVE gate
Moving version of the same boxes: Interactive.