# Phone systems

Lumate's agents talk on the phone through **OpenAI's SIP interface** (GPT-Live). A call reaches OpenAI over SIP; OpenAI tells Lumate with a signed webhook; Lumate accepts it with the agent's voice prompt and tools, and follows the call until it ends.

For an IP phone on the company network that talks to an agent (no trunk, no public address), see **Desk phone** in the [User guide](/docs/agents#desk-phone).

This page covers:
- how Lumate calls a customer, on which line, with examples (the next section);
- section 1: what works today and how to connect a number;
- section 2: when you need Asterisk between your carrier and OpenAI, with a working configuration;
- section 3: what phase 3 (live listening and take-over through Asterisk) adds — planned, not built yet.

## How Lumate calls a customer

Lumate has no phone line of its own. It calls on a line the company already has, the way someone at a desk in the
office does.

![Diagram: How Lumate calls a customer](/docs-diagrams/0d36bf956d14d85a.png)

**The lines.** Calls asked for in a chat, **Call now**, call lists and the dialer go out on the office line, else
through WhatsApp Web:
1. **The office line** (**Settings → Desk phone**). Lumate is one extension on the company's PBX. It dials the
   customer's number with the outside-line prefix (for example `9`, set in **Settings → Call center**), and the PBX
   sends the call out on its carrier line, as it does for anyone in the office. The customer sees the company's
   number: the PBX sets it, Lumate cannot. On the server edition the extension's connection runs on a linked office
   PC (**Settings → Desk phone → Connection runs on**).
2. **WhatsApp Web,** when the office line is off. Lumate calls from the WhatsApp number linked in the office browser.
   The customer sees that number and needs WhatsApp. WhatsApp calls do not connect in the UAE. See
   [Office browser → Calls through WhatsApp Web](/docs/office-browser#calls-through-whatsapp-web).

A number connected straight to Lumate (**Settings → Phone**, server edition only) is a third way: a public number that
Lumate answers itself (sections 1 and 2).

**What the office line needs:**
- **A PBX that can call out.** If people in the office can call a customer's mobile from their desk phones, the PBX
  has a carrier line, and Lumate uses the same one.
- **The extension's rights.** IT lets Lumate's extension call outside numbers and hold as many calls at once as the
  call center runs (see [The desk phone and the PBX](/docs/install-windows#the-desk-phone-and-the-pbx)).
- **No PBX in the office?** A hosted PBX (for example 3CX in the cloud) with a SIP trunk from a carrier gives one.
  Without either, Lumate calls through WhatsApp Web only.

**What starts a call:**

| You | What Lumate does | Checks |
|---|---|---|
| The owner or an admin, in a chat or a voice talk: "Call Ali Yılmaz and ask whether tomorrow at 3 suits him" | One call, now or at the time you give | It asks you first only outside 08:00–21:00, or for a number that asked not to be called ([Calls Lumate makes for you](/docs/calls#calls-lumate-makes-for-you)) |
| **Call now** on a customer's card | One call to that customer | The call center's: a recorded reason, a recent DNCR check, calling hours and limits |
| A call list: "call the To call cards", or an Excel file, then **Start** | One call after another | The same |
| The dialer (**Settings → Call center → Dialer**) | Calls due cards every half minute | The same |

### Examples

**A meeting to confirm (the office line).**
1. The owner writes in the chat: "Call Ali Yılmaz and ask whether tomorrow at 3 still suits him for coffee."
2. Lumate finds Ali's number on his card and dials it from extension 100, with the outside-line prefix.
3. The PBX sends the call out on its carrier line. Ali's phone shows the office's number.
4. Lumate greets him as the owner's assistant and asks right away. Ali says 4 o'clock at the same café suits him
   better.
5. The meeting goes into the owner's **Appointments** as a personal meeting, and Lumate writes in the chat that Ali
   moved it to 4.

**A call list, and a customer who wants a broker.**
1. An admin attaches an Excel file of 40 enquiries in the chat and writes "add these and call them". On the call list
   card they record why these people may be called (**Record why**), then press **Start**.
2. Lumate calls them one after another on the office line. Each one sees the company's number.
3. Sara is interested and agrees to talk to a broker. Lumate says it is connecting her to a colleague; she waits with
   a quiet tone while her card's broker's extension rings.
4. The broker answers and hears in one sentence who is calling and why; Lumate joins them and leaves the call. The
   broker's Lumate Desk shows Sara's card.
5. Had the broker not answered, Sara would have stayed with Lumate, which offers her a call back.

**The customer calls back (reception).**
1. IT has set the PBX to send the company's number to extension 100 (or to a menu option that ends there), and
   **Lumate answers callers on the office line** is on (**Settings → Call center → Incoming calls**).
2. Ali calls the company's number the next day. Lumate answers in his language.
3. He asks for Mehmet. Lumate says it checks whether Mehmet is free; Ali waits with a quiet tone while Mehmet's
   extension rings. Mehmet answers, hears who is calling and why, and Lumate puts Ali through.
4. Had Mehmet been in a meeting, Ali would have come back to Lumate: it says Mehmet cannot take the call right now
   and offers to take a message or arrange a call back. Ali is never dropped, and never hears an extension.

**A small office without a PBX (WhatsApp Web).**
1. **Settings → Desk phone** stays off. An admin links WhatsApp Web in the office browser (**Settings → Browser → Link
   WhatsApp Web**).
2. "Call Ayşe Kaya" goes out from the company's WhatsApp number. Ayşe sees that number and answers in WhatsApp.
3. A customer without WhatsApp cannot be reached this way, nor can a call that WhatsApp does not connect (the UAE).

## 1. What works today

![Diagram: 1. What works today](/docs-diagrams/1504884f2d21e192.png)

- **Inbound:** a number rings; the number's agent answers with the recording notice, follows the inbound scenario, records appointment requests, transfers to a person on request, and logs the call on the customer's card.
- **Outbound:** "Call now" on a card, or the agent's own calls. These stay **off** until an admin enables them with the trunk, and every call passes the compliance gate:
  - a consent basis;
  - the do-not-call list;
  - calling hours in the company's time zone;
  - attempt limits.
- **After every call:**
  - the transcript is on the card and on the Calls page;
  - the card moves;
  - the card's agent is woken to log the call;
  - a reviewer gets a review task if one is set.

### Connect a number

1. **Settings → Secrets:** add `OPENAI_API_KEY` from an OpenAI project with SIP enabled.
2. **Settings → Phone:** enter the OpenAI **project id**. The page shows the SIP address to dial, `sip:proj_…@sip.api.openai.com;transport=tls`.
3. **OpenAI dashboard** (platform.openai.com → Webhooks):
   - add a webhook for `live.transport.incoming` with the URL the Phone page shows;
   - copy its signing secret into Settings → Secrets as `OPENAI_WEBHOOK_SECRET`.

   The server must be reachable over HTTPS at its public address.
4. Point your trunk or PBX at the SIP address from step 2. **TLS and SRTP are required.** If your carrier cannot do them, put Asterisk in between (section 2).
5. **Settings → Phone → Numbers:** add the number. Choose:
   - the agent that answers;
   - an inbound scenario (optional);
   - who reviews each call;
   - the transfer number.
6. **For outbound:** in Settings → Phone, enable outbound with:
   - the trunk's `sips:` address;
   - the username;
   - the caller ID;
   - the password, saved as `SIP_TRUNK_PASSWORD` in Secrets.

Test it: call the number from a mobile phone. The agent answers, says the recording notice, and the call appears on the Calls page.

## 2. Asterisk between the carrier and OpenAI

UAE carrier trunks (du, e&) usually deliver plain SIP over UDP with G.711 (A-law) audio. OpenAI requires SIP over **TLS** with **SRTP** media. Asterisk translates between the two: the carrier talks to Asterisk, and Asterisk talks to OpenAI.

![Diagram: 2. Asterisk between the carrier and OpenAI](/docs-diagrams/61b12acb6b6860d6.png)

### What you need

| Item | Detail |
|---|---|
| Server | A Linux VM with a **public IPv4** address. Not behind home NAT: OpenAI sends media straight to Asterisk's address. |
| Asterisk | **22 LTS**, at least 22.10.1 (security fixes); 22.11 is current. Build from source with `--with-pjproject-bundled`, `libsrtp2` and `libssl`. |
| Carrier details | The trunk's IP, port, transport, credentials or IP authentication, and your DIDs (numbers) |
| Firewall | Allow 5060/udp from the carrier only; 5061/tcp out to OpenAI; RTP 10000–10199/udp from the carrier and OpenAI's media ranges (13.79.45.80/28, 23.98.140.64/28, 40.67.149.176/28, 40.83.204.240/28) |

### `pjsip.conf` (example to adapt)

```ini
[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0:5060
external_media_address=PUBLIC_IP
external_signaling_address=PUBLIC_IP
local_net=10.0.0.0/8

[transport-tls]
type=transport
protocol=tls
bind=0.0.0.0:5061
cert_file=/etc/asterisk/keys/asterisk.crt
priv_key_file=/etc/asterisk/keys/asterisk.key
ca_list_file=/etc/ssl/certs/ca-certificates.crt
method=tlsv1_2
verify_server=yes
external_media_address=PUBLIC_IP
external_signaling_address=PUBLIC_IP

; --- the carrier (IP-authenticated trunk) ---
[carrier]
type=endpoint
transport=transport-udp
context=from-carrier
disallow=all
allow=alaw,ulaw
direct_media=no
rtp_symmetric=yes
force_rport=yes
rewrite_contact=yes
aors=carrier
[carrier]
type=aor
contact=sip:CARRIER_IP:5060
[carrier]
type=identify
endpoint=carrier
match=CARRIER_IP

; --- OpenAI ---
[openai]
type=endpoint
transport=transport-tls
context=from-openai
aors=openai
disallow=all
allow=alaw,ulaw,opus
media_encryption=sdes
direct_media=no
rtp_symmetric=yes
force_rport=yes
rewrite_contact=yes
allow_transfer=no
[openai]
type=aor
contact=sip:sip.api.openai.com:5061;transport=tls
qualify_frequency=0
[openai]
type=identify
endpoint=openai
match=sip.api.openai.com
```

### `extensions.conf` (example)

```ini
[from-carrier]
; every call to one of your numbers goes to OpenAI with the dialled number kept
exten => _X.,1,NoOp(Inbound ${EXTEN} from ${CALLERID(num)})
 same => n,Dial(PJSIP/openai/sip:PROJECT_ID@sip.api.openai.com;transport=tls,60)
 same => n,Hangup()

[from-openai]
; outbound calls Lumate asks OpenAI to place, and transfers to a person
exten => _+X.,1,NoOp(Outbound ${EXTEN})
 same => n,Set(CALLERID(num)=YOUR_CALLER_ID)
 same => n,Dial(PJSIP/${EXTEN}@carrier,60)
 same => n,Hangup()
```

**Notes:**
- **Keep the trunk out of reach.** Never allow an anonymous endpoint, and never let a context that untrusted callers reach dial the trunk. `from-openai` is reached only by the `openai` endpoint, matched by host.
- **Use G.711 end to end where you can.** Carriers use A-law, so offering `alaw` first to OpenAI avoids transcoding.
- **Point Lumate at Asterisk for outbound.** In Settings → Phone, the trunk's `sips:` address becomes your Asterisk server, `sips:PUBLIC_HOSTNAME:5061`, and Asterisk forwards to the carrier.
- **Use a real certificate.** Asterisk needs one for the TLS transport (Let's Encrypt for its host name).

### Checking it

| Command (`asterisk -rvvv`) | Expected |
|---|---|
| `pjsip show endpoints` | `carrier` and `openai` Available |
| `pjsip show transports` | udp on 5060, tls on 5061 |
| `rtp set debug on` during a call | packets both ways; if one-way, check `external_media_address` and the firewall |

**Common problems:**
- **Silence both ways:** RTP is blocked, or the address is wrong.
- **488 Not Acceptable:** no common codec, or SRTP is missing.
- **The call drops after 30 s:** a known OpenAI interop issue; update Asterisk, and report it with the call id.

## 3. Phase 3 — live listening and take-over (planned)

Not built yet. The plan, so you can prepare the server:

![Diagram: 3. Phase 3 — live listening and take-over (planned)](/docs-diagrams/4424c498043d6042.png)

- **How calls will be handled.** Every call goes through an Asterisk *Stasis* application. Lumate then controls each call with ARI: bridges for the call itself, *snoop* channels for listening.
- **The manager's browser phone.** A manager opens a live call in Lumate and the browser joins:
  - **listen**: muted;
  - **whisper**: only the agent hears;
  - **take over**: the AI leg leaves and the manager talks to the customer.

  The browser phone uses WebRTC through Lumate's own HTTPS address; ARI is never exposed.
- **Server:** our own Asterisk 22 image in the compose profile `phone`. It publishes only 5061/tcp, the RTP range and the trunk port.
- **Open questions for the first live test:**
  - whether OpenAI passes custom `X-` SIP headers in the webhook;
  - codec negotiation, Opus versus G.711;
  - NAT and RTP on the chosen VM.

## Law and good practice (UAE)

- Every call opens with a recording notice, and agents never deny being an AI.
- **Do-not-call:**
  - "Don't call me" marks the card do-not-call and adds the number to the company's do-not-call list;
  - outbound calls check that list and the registry attestation.
- Outbound calls only within the company's calling hours, and only with a consent basis.
- Cold calling, marketing messages, call monitoring disclosure and recording rules in the UAE and KSA need a lawyer's sign-off before outbound calling starts.
