# Events

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

`CallEvent` is how the gateway tells you what happened to a call after you
hand the call off. The gateway pushes events 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`    | A 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`    | An echo of `EventStreamRequest.client_id`.       |

## Event types

| `event_type` value | Name                       | Fired                                                 |
| ------------------ | -------------------------- | ----------------------------------------------------- |
| `0` `UNKNOWN`      | reserved                   | This value must never appear. If it appears, log the event and drop it. |
| `1` `CHANNEL_CREATE` | The gateway created the b-leg (callee). Do not assume audio yet. |                                            |
| `2` `CHANNEL_ANSWER` | The far end answered. Audio now flows.              |                                                       |
| `3` `CHANNEL_HANGUP_COMPLETE` | The final accounting event. It includes the Q.850 cause and stats. |                              |
| `4` `CHANNEL_HOLD` | The caller pressed hold (or `ExecuteDialplan(MUSIC_ON_HOLD)`). |                                            |
| `5` `CHANNEL_RESUME` | The hold ended.                                    |                                                       |

## 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 connected SDK instances.

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

After this RPC, the gateway opens server-initiated uni-streams to your
session. These streams contain audio and event frames.

## Ordering

For a single `call_sid`, the gateway emits events strictly in this order:

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

Across `call_sid`s, events are not ordered. The gateway pushes them as
fast as it can serialize them.

## Reconnect behaviour

- **Within a session:** If a uni-stream resets mid-frame, the gateway
  opens a new one. It replays the latest `CHANNEL_HANGUP_COMPLETE` for
  each call that completed during the gap. The gateway does not replay
  mid-call events, because you 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`. It drops anything older.

## Recommended consumer pattern

Track in-flight `call_sid`s in a set. Remove them on
`CHANNEL_HANGUP_COMPLETE`. Do not drive UI from `status` strings alone.
Those strings are for humans. Branch on `event_type`. 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)
```
