# Events

> Server-pushed CallEvent semantics, ordering, and reconnect behaviour.

`CallEvent` is how the gateway tells you what happened to a call after you
hand it off. Events are pushed on the same uni-stream pipe as audio.

## `CallEvent` schema

| Field                 | Type        | When populated                                   |
| --------------------- | ----------- | ------------------------------------------------ |
| `call_sid`            | `string`    | Always.                                          |
| `event_type`          | `EventType` | Always (see table below).                        |
| `status`              | `string`    | Coarse string for human reading: `INITIATED`, `RINGING`, `ANSWERED`, `COMPLETED`, `FAILED`, `BUSY`, `NO_ANSWER`. |
| `start_timestamp_ms`  | `int64`     | `CHANNEL_CREATE` and later events.               |
| `q850_cause`          | `int32`     | `CHANNEL_HANGUP_COMPLETE`. ITU-T Q.850 cause.    |
| `recording_url`       | `string`    | `CHANNEL_HANGUP_COMPLETE` if recording on.       |
| `duration_seconds`    | `int32`     | `CHANNEL_HANGUP_COMPLETE`.                       |
| `answer_timestamp_ms` | `int64`     | `CHANNEL_ANSWER` and later.                      |
| `end_timestamp_ms`    | `int64`     | `CHANNEL_HANGUP_COMPLETE`.                       |
| `packets_sent`        | `uint32`    | `CHANNEL_HANGUP_COMPLETE`.                       |
| `packets_received`    | `uint32`    | `CHANNEL_HANGUP_COMPLETE`.                       |
| `packets_lost`        | `uint32`    | `CHANNEL_HANGUP_COMPLETE`.                       |
| `bytes_sent`          | `uint64`    | `CHANNEL_HANGUP_COMPLETE`.                       |
| `jitter_ms`           | `double`    | `CHANNEL_HANGUP_COMPLETE`.                       |
| `estimated_mos`       | `double`    | `CHANNEL_HANGUP_COMPLETE`. R-factor → MOS estimate. |
| `trunk_id`            | `string`    | Always.                                          |
| `tenant_id`           | `string`    | Always.                                          |
| `codec`               | `string`    | Negotiated egress codec.                         |
| `timestamp_ms`        | `int64`     | When the gateway emitted this event.             |
| `client_id`           | `string`    | Echo of `EventStreamRequest.client_id`.          |

## Event types

| `event_type` value | Name                       | Fired                                                 |
| ------------------ | -------------------------- | ----------------------------------------------------- |
| `0` `UNKNOWN`      | reserved                   | Should never appear. If it does, log and drop.        |
| `1` `CHANNEL_CREATE` | b-leg (callee) was created. Do not assume audio yet. |                                                       |
| `2` `CHANNEL_ANSWER` | Far end picked up; audio is flowing.                |                                                       |
| `3` `CHANNEL_HANGUP_COMPLETE` | Final accounting event. Includes Q.850 cause and stats. |                                          |
| `4` `CHANNEL_HOLD` | Caller pressed hold (or `ExecuteDialplan(MUSIC_ON_HOLD)`). |                                                |
| `5` `CHANNEL_RESUME` | Hold lifted.                                       |                                                       |

## Subscribing

Send one `EventStreamRequest` immediately after connect. The SDKs do this
for you in `connect()` / `connect_async()`. The `client_id` should be a
stable per-process UUID — the gateway uses it to demux events when the
same tenant has multiple SDK instances connected.

```
[u32 length][u32 method_id=959835745][envelope: client_id="..."]
```

After this RPC, the gateway will open server-initiated uni-streams to your
session containing audio and event frames.

## Ordering

For a single `call_sid`, events are emitted strictly in this order:

```
CHANNEL_CREATE  →  (CHANNEL_ANSWER  →  CHANNEL_HOLD ⇄ CHANNEL_RESUME …)?  →  CHANNEL_HANGUP_COMPLETE
```

Across `call_sid`s, events are not ordered — they're just pushed as fast
as the gateway can serialize them.

## Reconnect behaviour

- **Within a session:** If a uni-stream resets mid-frame, the gateway will
  open a new one and replay the latest `CHANNEL_HANGUP_COMPLETE` for any
  call that completed during the gap. Mid-call events are not replayed —
  there's no point, you've already missed the audio.
- **Across sessions:** A fresh QUIC connection with the same `client_id`
  resumes the subscription. The gateway holds an event buffer of
  120 seconds per `client_id`; anything older is dropped.

## Recommended consumer pattern

Track in-flight `call_sid`s in a set; remove them on `CHANNEL_HANGUP_COMPLETE`.
Don't try to drive UI off `status` strings alone — they're for humans.
Branch on `event_type` and use `q850_cause` to disambiguate hang-up reasons.

```python
ACTIVE: set[str] = set()

def on_event(ev):
    if ev.event_type == EventType.CHANNEL_CREATE:
        ACTIVE.add(ev.call_sid)
    elif ev.event_type == EventType.CHANNEL_HANGUP_COMPLETE:
        ACTIVE.discard(ev.call_sid)
        if ev.q850_cause in (17, 18, 19):  # busy / no_user_response / no_answer
            schedule_retry(ev.call_sid)
```
