Calls

Take voice calls: HAI streams the caller's voice to your server and plays your server's voice back.

A person can call your agent from its chat or profile in HAI. The call stays end-to-end encrypted to your agent: HAI's call server never hears it, and the voice is decrypted only inside HAI's call bridge for your agent, then streamed to your server over one WebSocket per call.

Turn calls on

On the agent's Calls page, set the calls address (a public wss:// address on port 443) and turn on Audio calls. HAI places a test call at once. The call button appears in people's apps only after a test call passes, and disappears again after three failures in a row; HAI checks every hour and after every call that could not reach you. There is no checkbox people see: the button means the line works.

The WebSocket

For each call HAI opens wss://your-address with the header HAI-Signature. The first message is a text message, the start message. The signature is the one events carry (Events) with call. in front of the signed text: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, "call.<t>.<start message>")>, over exactly the start message's bytes. So a start message never passes as an event, nor an event as a call. Check it before anything else and close the connection if it does not match or is older than five minutes. Use its id to drop a repeat.

The start message arrives while the person's phone is still ringing. HAI answers the call a moment later, once your server and the agent are both on the line. Until then you receive none of the caller's audio, and the audio you send is held (up to 20 seconds) and played from the moment the call connects.

{
  "type": "call.start", "id": "call_8Fq2…", "created_at": "2026-10-23T11:02:07Z",
  "agent": {"id": "AG12…", "name": "Leave Bot"},
  "call": {"id": "8Fq2…", "chat_id": "u1_u2", "max_seconds": 1800},
  "from": {"uid": "u1", "name": "Sara", "phone": "+9665…"},
  "audio": {"encoding": "pcm16le", "sample_rate": 24000, "channels": 1}
}

from.phone follows the same rule as events: only a number your company listed or the person shares in that chat. A test call carries "test": true and no call or from.

Audio

  • From HAI: binary messages of raw PCM, 16-bit little-endian, mono, 24 kHz, 20 ms each (960 bytes): the caller's voice.
  • To HAI: binary messages in the same format, any length up to 64 KB (an even number of bytes): the agent's voice. HAI plays them in order. Up to 20 seconds may be queued ahead; more is dropped. Send at most 250 messages a second (2,500 at once, all kinds counted, pings too); a server that sends more is cut off and the call ends. Pieces of 20 ms or more stay far below it.
  • Speak first. Send a greeting as soon as the start message arrives: HAI plays it the moment the call connects. A test call passes when audio comes back within five seconds (a greeting or an echo both pass).

Control messages

MessageWho sends itMeaning
{"type":"clear"}Your serverDrop the agent's audio still queued (the caller interrupted).
{"type":"hangup"}Your serverEnd the call for both sides.
{"type":"call.end","reason":"…"}HAIThe call is over: caller_left, hangup, time_limit, room_closed, no_caller, not_answered (it never connected: the person hung up while it rang), join_failed, stopped (HAI's call service restarted; calls in progress end) or test_done. The connection closes next.

A minimal server

# Python (websockets 13+)
import asyncio, hmac, hashlib, json, time, websockets
SECRET = "your signing secret"
def valid(header, body):
    parts = dict(p.split("=", 1) for p in header.split(","))
    t, v1 = parts.get("t", ""), parts.get("v1", "")
    if not t.isdigit() or abs(time.time() - int(t)) > 300: return False
    mac = hmac.new(SECRET.encode(), f"call.{t}.".encode() + body.encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(mac, v1)
async def call(ws):
    start = await ws.recv()
    if not valid(ws.request.headers.get("HAI-Signature", ""), start):
        return await ws.close(4001)
    async for msg in ws:          # echo: send the caller's voice back
        if isinstance(msg, bytes): await ws.send(msg)
asyncio.run(websockets.serve(call, "0.0.0.0", 8080).serve_forever())

A real agent streams the caller's PCM into a speech model (most realtime voice models take 24 kHz PCM16 directly) and streams the model's voice back.

Who can call

  • One-to-one calls, audio only, started by a person. Groups and video come later.
  • An agent waiting for HUMAIN's approval takes calls only from its developers and testers.
  • A call lasts at most 30 minutes (less when the agent's day is nearly used up: call.max_seconds in the start message says how long). An agent takes at most 5 calls at once and 300 minutes a day; all of one developer's agents together take at most 10 calls at once.
  • The agent leaves the call the moment the caller does.