> For the complete documentation index, see [llms.txt](https://docs.concurrence.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.concurrence.com/developer-guide/platform-api/conversations/sse-events.md).

# SSE Events

Real-time workspace event stream delivered via Server-Sent Events (SSE).

The platform publishes real-time events to workspace subscribers via Server-Sent Events (SSE). Each event has a fixed `event_type` discriminator and a typed payload. Events are immutable once published.

Fields that may contain protected health information are annotated in the schema with `x-phi: true`. Consumers that persist or log event payloads can use this annotation to apply redaction or access controls.

## Consuming the Stream

The stream is a standard SSE connection authenticated with a workspace-scoped credential, and every event is scoped to the workspace that produced it. Clients should reconnect automatically with backoff when the connection drops. New event types may be added over time - ignore event types you do not recognize rather than treating them as errors.

{% openapi src="<https://platform.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/events/stream" method="get" %}
<https://platform.api-us.concurrence.com/v1/openapi.json>
{% endopenapi %}

### Connection Frames

Besides business events, the stream sends a few frames that manage the connection:

| Frame                                                      | Meaning                                                                                                                                                                        |
| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `retry:` line                                              | The reconnect delay the server asks for. Standard SSE clients apply it.                                                                                                        |
| `stream.opened` event                                      | The stream is live. `data` carries `code: "ok"` and a `connection_id`.                                                                                                         |
| `: heartbeat` comment line                                 | Keeps a quiet stream open. It is not an event; skip it.                                                                                                                        |
| `error` event with `code: "too_many_streams"`              | The workspace has too many streams open, and this one is closed. Close streams you don't need and wait before reconnecting; the frame's `retry:` line asks for a longer delay. |
| `error` event with `code: "stream_unavailable"`            | Event streaming is unavailable, and the stream ends. Reconnect with backoff.                                                                                                   |
| `stream.expired` event with `code: "max_duration_reached"` | The stream reached its maximum lifetime and ends. Reconnect with the last event ID to continue.                                                                                |

Each business event carries an `id:` line. Send the last one you received as `Last-Event-ID` when you reconnect, and the stream replays recent events you missed, on a best-effort basis.

### Scope to One Conversation

Pass `conversation_id` to receive one web conversation's own events, with their text: `text.agent_message`, `text.tool_started`, and `text.background_result`. These three events are published only to that conversation's stream, so the unscoped workspace stream and its replay buffer never carry them. Workspace events, including `text.started`, `text.completed`, and call and surface events, stay on the unscoped stream and are not sent on a scoped one. A `conversation_id` that is not a UUID returns `422`. Connection frames are the same on both streams.

<figure><img src="https://1651797362-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F2YwM0LHtEGLt9S21OyC3%2Fuploads%2Fgit-blob-052af5480c37e8c258d5772190dd3efaf86c2dec%2Fevent-stream-scope.svg?alt=media" alt="The workspace stream carries workspace events, such as text.started, call.ended, and surface events, with IDs and metadata and no message text. A stream opened with conversation_id carries one web conversation&#x27;s message, tool, and background-result events with their text."><figcaption><p>Chat clients take replies from turns, <code>poll=true</code>, and conversation detail; the streams are for observing.</p></figcaption></figure>

A scoped stream replays its own recent events on `Last-Event-ID`, on a best-effort basis: a reconnect can miss events that arrived while no stream for that conversation was open. Treat the conversation's turns as the source of truth, and use the [turn, poll, and detail operations](/developer-guide/platform-api/conversations.md#background-deferred-tool-results) to deliver replies to a chat client. A `text.agent_message` event can carry a `delivery_id` that matches the `delivery` a poll returns for the same answer, so a client that watches both can show the answer once.

## Workspace Events

### Call Events

| Event Type       | Description                         | Key Fields                                                                    |
| ---------------- | ----------------------------------- | ----------------------------------------------------------------------------- |
| `call.started`   | A call session began                | `call_sid`, `direction`, `service_id`, `caller_id`                            |
| `call.ended`     | A call session ended                | `call_sid`, `direction`, `duration_seconds`, `completion_reason`, `escalated` |
| `call.escalated` | A call was escalated to an operator | `call_sid`, `escalation_id`, `trigger`, `reason`                              |

### Text Events

| Event Type               | Description                                                                           | Key Fields                                                                                         |
| ------------------------ | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `text.started`           | A text session began                                                                  | `session_id`, `service_id`, `channel_kind`, `entity_id`                                            |
| `text.completed`         | A text session ended                                                                  | `session_id`, `duration_seconds`, `turn_count`, `completion_reason`, `final_state`, `channel_kind` |
| `text.agent_message`     | The agent produced a message in a text conversation (conversation-scoped stream only) | `conversation_id`, `text`, `delivery_id`                                                           |
| `text.tool_started`      | A tool call started during a text turn (conversation-scoped stream only)              | `conversation_id`, `call_id`, `tool_name`, `depth`                                                 |
| `text.background_result` | A background tool call finished (conversation-scoped stream only)                     | `conversation_id`, `call_id`, `tool_name`, `depth`, `succeeded`, `result`, `duration_ms`           |

### Surface Events

| Event Type                | Description                           | Key Fields                                                                                                                             |
| ------------------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `surface.created`         | A surface was created                 | `surface_id`, `entity_id`, `channel`, `fields_count`, `source`, `form_template_id`, `review_required`                                  |
| `surface.delivered`       | A surface was delivered to a patient  | `surface_id`, `entity_id`, `channel`, `channel_address`, `delivery_status`, `message_id`, `short_url`                                  |
| `surface.updated`         | A surface was updated                 | `surface_id`                                                                                                                           |
| `surface.archived`        | A surface was archived                | `surface_id`                                                                                                                           |
| `surface.submitted`       | A surface was submitted by a patient  | `surface_id`, `entity_id`, `submitted_at`, `fields_submitted`, `conversation_id` (null for a surface made outside a text conversation) |
| `surface.field_saved`     | A single field was saved on a surface | `surface_id`, `entity_id`, `field_key`                                                                                                 |
| `surface.opened`          | A surface was opened by a patient     | `surface_id`, `entity_id`                                                                                                              |
| `surface.reshaped`        | A surface was reshaped                | `surface_id`, `original_surface_id`                                                                                                    |
| `surface.pending_review`  | A surface is pending review           | `surface_id`, `entity_id`, `source`, `form_template_id`, `review_required`                                                             |
| `surface.review_approved` | A surface review was approved         | `surface_id`                                                                                                                           |
| `surface.review_rejected` | A surface review was rejected         | `surface_id`, `reason`                                                                                                                 |

### Encounter Events

| Event Type          | Description                     | Key Fields                          |
| ------------------- | ------------------------------- | ----------------------------------- |
| `encounter.updated` | An encounter entity was updated | `encounter_entity_id`, `session_id` |

### Narrative Events

| Event Type                   | Description                                  | Key Fields                                                                                   |
| ---------------------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------- |
| `self_image.brief_generated` | A narrative brief was generated or refreshed | `target_entity_id`, `target_entity_type`, `event_id`, `version`, `confidence`, `event_count` |

### Review Events

| Event Type         | Description               | Key Fields                                               |
| ------------------ | ------------------------- | -------------------------------------------------------- |
| `review.submitted` | A review action was taken | `action`, `item_id`, `entity_id`, `entity_type`, `count` |

The `action` field is one of: `approve`, `reject`, `correct`, `batch_approve`, `batch_reject`.

### Simulation Events

| Event Type               | Description                    | Key Fields                                                                                       |
| ------------------------ | ------------------------------ | ------------------------------------------------------------------------------------------------ |
| `simulation.turn_stored` | A simulation turn was recorded | `service_id`, `run_id`, `session_id`, `turn_index`, `state_before`, `state_after`, `is_terminal` |

### Trigger Events

| Event Type          | Description                                  | Key Fields                             |
| ------------------- | -------------------------------------------- | -------------------------------------- |
| `trigger.fired`     | An automation trigger was activated          | `trigger_id`, `trigger_name`, `manual` |
| `trigger.completed` | An automation trigger completed successfully | `trigger_id`, `trigger_name`, `status` |
| `trigger.failed`    | An automation trigger failed                 | `trigger_id`, `trigger_name`, `status` |

### Operator Events

| Event Type                 | Description                          | Key Fields                                            |
| -------------------------- | ------------------------------------ | ----------------------------------------------------- |
| `operator.registered`      | An operator was registered           | `operator_id`, `timestamp`                            |
| `operator.status_changed`  | An operator's status changed         | `operator_id`, `status`                               |
| `operator.profile_updated` | An operator's profile was updated    | `operator_id`                                         |
| `operator.joined_call`     | An operator joined a call            | `call_sid`, `operator_id`, `mode`                     |
| `operator.left_call`       | An operator left a call              | `call_sid`, `operator_id`, `human_segment_turn_count` |
| `operator.mode_changed`    | An operator's mode changed on a call | `call_sid`, `operator_id`, `mode`                     |
| `operator.wrap_up`         | An operator entered wrap-up          | `call_sid`, `wrap_up_reason`                          |

Operator `status` is one of: `online`, `busy`, `offline`. Operator `mode` is one of: `listen`, `takeover`.

### Integration Approval Events

| Event Type                      | Description                              | Key Fields                  |
| ------------------------------- | ---------------------------------------- | --------------------------- |
| `integration.approval_granted`  | A pending integration write was approved | `conversation_id`           |
| `integration.approval_rejected` | A pending integration write was rejected | `conversation_id`, `reason` |

### Workspace Membership Events

| Event Type                      | Description                         | Key Fields                           |
| ------------------------------- | ----------------------------------- | ------------------------------------ |
| `workspace.invitation_sent`     | A workspace invitation was sent     | `invitation_id`, `role`, `resent`    |
| `workspace.invitation_accepted` | A workspace invitation was accepted | `entity_id`, `role`                  |
| `workspace.member_added`        | A member was added to the workspace | `entity_id`, `role`                  |
| `workspace.member_role_updated` | A member's role changed             | `entity_id`, `previous_role`, `role` |

### Channel Email Events

Email delivery status events are published when email is sent through a use case channel, letting you track delivery outcomes in real time. They are emitted automatically when the platform receives delivery status updates from the email provider - no configuration is required beyond creating a use case with an email channel.

| Event Type                 | Description                      | Key Fields                                                                          |
| -------------------------- | -------------------------------- | ----------------------------------------------------------------------------------- |
| `channel.email_delivered`  | Email successfully delivered     | `email_id`, `to_address`, `from_address`                                            |
| `channel.email_bounced`    | Email bounced                    | `email_id`, `to_address`, `from_address`, `bounce_type`, `bounce_subtype`           |
| `channel.email_complained` | Recipient filed a spam complaint | `email_id`, `to_address`, `from_address`                                            |
| `channel.email_rejected`   | Email rejected before delivery   | `email_id`, `to_address`, `from_address`                                            |
| `channel.email_delayed`    | Delivery delayed, retrying       | `email_id`, `to_address`, `from_address`                                            |
| `channel.email_opened`     | Recipient opened the email       | `email_id`, `to_address`                                                            |
| `channel.email_clicked`    | Recipient clicked a link         | `email_id`, `to_address`                                                            |
| `channel.email_received`   | An inbound email was received    | `inbound_email_id`, `from_address`, `to_address`, `subject`, `in_reply_to_email_id` |

## Discriminated Unions in the SDK

The OpenAPI specification includes typed schemas for all event types as a discriminated union on the `event_type` field. SDK clients generated from the spec (such as the TypeScript SDK) receive full type safety when handling events:

```typescript
// TypeScript SDK example - exhaustive event handling
switch (event.event_type) {
  case "call.started":
    console.log(`Call ${event.call_sid} started`);
    break;
  case "surface.created":
    console.log(`Surface ${event.surface_id} created for entity ${event.entity_id}`);
    break;
  // ... other event types
}
```

## Observer Events

A separate WebSocket stream delivers per-call observer events (transcripts, state transitions, tool calls, emotion analysis, latency timing) to clients monitoring an active voice session. Observer events use a `type` discriminator rather than `event_type`. The full catalog, field lists, and PHI annotations for observer events live on the [Observer Events](/developer-guide/platform-api/conversations/observer-events.md) page, which is the authoritative reference.

{% content-ref url="/pages/Qm4yWkfviMbMOVLEbCzJ" %}
[Observer Events](/developer-guide/platform-api/conversations/observer-events.md)
{% endcontent-ref %}

## PHI Annotations

The following workspace event fields are annotated as potentially containing PHI:

| Event                      | Field                        |
| -------------------------- | ---------------------------- |
| `call.started`             | `caller_id`                  |
| `surface.delivered`        | `channel_address`            |
| `channel.email_*`          | `to_address`                 |
| `channel.email_received`   | `from_address`, `to_address` |
| `channel.voicemail_status` | `recipient_phone_number`     |

PHI annotations for observer event fields are listed on the [Observer Events](/developer-guide/platform-api/conversations/observer-events.md#phi-annotations) page.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.concurrence.com/developer-guide/platform-api/conversations/sse-events.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
