Ir al contenido
ES
Abrir la app

For AI assistants: this page as plain markdown is at https://docs.voicelogica.ai/raw/telephony/live-call-analysis.md, and https://docs.voicelogica.ai/llms.txt lists the docs by topic, one file each, small enough to read whole. Read those rather than this HTML page.

Live call analysis by a partner

8 min de lectura
Copiar como Markdown
Ver como MarkdownAbrir el archivo .md.md

Esta página aún no está disponible en tu idioma.

A partner service, for example fraud detection, can follow the calls on one of your lines (SIP trunks) while they happen and tell the AI on the call what to do.

  • Call events. When a call on the line is answered and when it ends, we POST a short JSON message to the partner.
  • Audio. For every call we open a WebSocket to the partner and stream both sides of the call.
  • Instructions back to the AI. The partner sends a message for a live call, for example “Warn the caller: asking for an OTP while posing as the bank is a known scam.” Only the AI sees it. The AI acts on it right away, in its own words and in the language of the call. The message never shows in the transcript.
  1. Open Telephony, then the line (SIP trunk) whose calls you want analysed.
  2. In Live call analysis, fill in the partner’s addresses and token (see the table below).
  3. Click Save live call analysis. From the next call on, every call on that line goes to the partner.

To stop, empty both addresses and save. You can also set it from your AI assistant with the set_trunk_call_stream tool. It is not available on Voice Logica numbers: use your own line.

FieldExample
Audio address (WebSocket)wss://partner.example.com:8443/voicelogica/stream
Events address (HTTP)https://partner.example.com:8443/voicelogica/events
TokenA long random string. We send it as Authorization: Bearer <token> on both.

Each address is a full URL with the port and path, for example wss://150.140.10.20:8443/stream. Use wss:// and https://: the audio and the phone numbers then travel encrypted. Plain ws:// and http:// are only for tests.

The partner can also accept connections only from our servers’ IP addresses. Ask us for the list.

For every call on the line, whether the AI answers it or it is connected to a person, we send a short JSON message to the events address as things happen: the call is answered, a transfer starts, works or fails, the call ends.

Each event’s event field is written subject.action: what it is about, then what happened. Every event today is about the call, so they all start with call.. Later we may add other subjects on the same address (for example a recording or an analysis), so route by the full name and ignore names you don’t know.

EventWhen it is sent
call.startedThe call is answered.
call.transfer_startedA transfer begins: the AI or a person is passing the call on.
call.transferredThe transfer worked: the caller is now with the new destination.
call.transfer_failedThe transfer did not happen: the recipient declined, was busy, did not answer, or the number failed.
call.transfer_cancelledThe transfer was dropped and the caller stays where they were.
call.endedThe call is over.

A call can have several transfers, one after the other. Each attempt sends its own transfer events.

Field
eventThe event name, from the table above.
callIdOur id for the call. Use it to send instructions to the AI on this call, and to match events and the audio stream of the same call.
sipCallIdThe SIP Call-ID of the call on your line, when there is one.
sipTrunkIdThe line (SIP trunk) the call is on.
directioninbound (someone called the line) or outbound (the line called someone).
from, toThe caller’s and the called number.
startedAtWhen the call started (ISO 8601, UTC).
EventExtra fields
call.transfer_started, call.transferred, call.transfer_cancelledtransfer.destination (the number or address), transfer.destinationType (e.g. external_number, user, user_group), transfer.mode (blind, attended or warm), transfer.reason (why the AI transferred, when it said).
call.transfer_failedThe same, plus transfer.failedReason: what happened, as recorded on the call, e.g. “Destination declined”, “busy”, “No answer after 30 seconds of ringing”.
call.endedendedAt, durationSeconds, status (e.g. answered, transferred, missed), reason (why it ended, e.g. caller_hungup).

A field we have no value for is left out rather than sent empty.

  • Request: POST to the events address, body application/json, header Authorization: Bearer <token>. Check the token on every request and refuse anything else.
  • Your reply: any 2xx within 5 seconds. The body is ignored.
  • Retries: if there is no 2xx (or no answer in 5 seconds) we try once more right away. If that fails too, the event is dropped and the failure is logged on the call. We never hold the call while we wait.
  • Once only: each event is sent once per call (once per attempt for transfer events), even when several of our servers handle the same call.
  • Order: events go out as the call changes, from whichever of our servers handled that change, so two events close together (for example call.transferred and call.ended) can arrive in either order. Go by the event name, not by arrival order.
  • New events and fields: we may add event names and fields without notice. Ignore what you don’t know. Removing or renaming one would be announced first.
{
"event": "call.started",
"callId": "6adf55e278e0d0de8ce090b2",
"sipCallId": "a84c3c7f-77c8@10.0.0.5",
"sipTrunkId": "69dcacb29e637bf355a3b834",
"direction": "inbound",
"from": "+306900000000",
"to": "+302100000000",
"startedAt": "2026-10-26T13:30:11.970Z"
}
{
"event": "call.ended",
"callId": "6adf55e278e0d0de8ce090b2",
"sipTrunkId": "69dcacb29e637bf355a3b834",
"direction": "inbound",
"from": "+306900000000",
"to": "+302100000000",
"startedAt": "2026-10-26T13:30:11.970Z",
"endedAt": "2026-10-26T13:31:31.242Z",
"durationSeconds": 79,
"status": "answered",
"reason": "caller_hungup"
}

Transfer events carry a transfer object:

{
"event": "call.transfer_failed",
"callId": "6adf55e278e0d0de8ce090b2",
"sipTrunkId": "69dcacb29e637bf355a3b834",
"direction": "inbound",
"from": "+306900000000",
"to": "+302100000000",
"startedAt": "2026-10-26T13:30:11.970Z",
"transfer": {
"destination": "+302109999999",
"destinationType": "external_number",
"mode": "warm",
"reason": "Caller asked for the accounts department",
"failedReason": "Destination declined"
}
}

callId is the id to use when you send instructions for this call.

One WebSocket per call, opened as soon as the call reaches us, with Authorization: Bearer <token>. Every call on the line is streamed, whether the AI answers it or it is connected to a person.

1. First message (text, JSON): the call and the audio format.

{
"event": "start",
"callId": "6adf55e278e0d0de8ce090b2",
"sipTrunkId": "69dcacb29e637bf355a3b834",
"direction": "inbound",
"from": "+306900000000",
"to": "+302100000000",
"startedAt": "2026-10-26T13:30:11.970Z",
"version": 1,
"bidirectional": false,
"audio": {
"encoding": "pcm_s16le",
"sampleRate": 8000,
"channels": 2,
"frameMs": 20,
"layout": ["caller", "callee"],
"framing": "channel_prefixed"
}
}

version is the protocol version, 1 today. bidirectional is false: we only send audio, and anything the partner sends back on the socket is ignored. Lines where the partner runs the whole call and its audio is played to the caller will use the same protocol with bidirectional: true, so check both fields.

2. Audio (binary): each message is one side of the call, 20 ms long.

ByteContent
0Side: 0 = the caller (the person on your line), 1 = the other side (the AI, or the person the call is connected to)
1 onward20 ms of PCM 16-bit little-endian, mono. At 8 kHz that is 320 bytes, so each message is 321 bytes.

The two sides come as separate messages, so you get each one whole, with no mixing and no silence added to line them up.

3. Last message (text, JSON) when the call ends. Then we close the connection. The reason the call ended comes in the call.ended event.

{ "event": "stop", "callId": "6adf55e278e0d0de8ce090b2" }

If the partner reads too slowly and more than about 1 MB is waiting to be sent, we drop audio frames until it catches up, so a slow partner never slows the call down.

Terminal window
curl -X POST https://api.voicelogica.ai/api/v1/phones/calls/6adf55e278e0d0de8ce090b2/instructions \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"message": "Warn the caller: asking for an OTP while posing as the bank is a known scam.", "type": "possible_scam", "severity": "high", "confidence": 0.87}'
Field
messageRequired, up to 2,000 characters. What the AI should do or say now.
typeOptional, e.g. possible_scam.
severityOptional, e.g. warning, high, critical.
confidenceOptional, 0 to 1. Kept in the call log only.

The API key needs the call.send_call_instruction permission. Create it on Team and access - API Keys in the company that owns the line (API Keys). From an AI assistant connected to Voice Logica (MCP), the same is the send_call_instruction tool.

Answer: 200 with { "callId": "...", "delivered": true } once the AI has the instruction. 400 when the call has ended, is not answered yet, or has no AI on it. 404 when the call is not one of this company’s calls.

Every instruction is kept in the call’s log with its type, severity and confidence.

Does the caller hear the instruction?

No. Only the AI sees it. The caller hears what the AI then says, in its own words.

Which calls are sent to the partner?

Every call on that line, audio and events, whether the AI answers it or it is connected to a person. To send only some calls, put them on a separate line.

Can the partner send several alerts on one call?

Yes. Each instruction is acted on when it arrives.