> ## Documentation Index
> Fetch the complete documentation index at: https://docs.osvi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Chat Delivery Webhook

> Osvi POSTs a chat agent's reply to the delivery URL configured on the agent whenever the reply can't be returned inline: for a turn accepted via [Send Message (Async)](/api-reference/endpoint/chat/inbound_async), and for any operator-authored message (a Tell, a plain support message, or a takeover reply) sent through [Send Support / Control Command](/api-reference/endpoint/chat/support). A session's most recent messages decide where its replies go: if the customer has been messaging over a [chat WebSocket](/api-reference/chat-websocket) connection, replies are delivered as events on that socket instead of to this webhook.

A turn can send more than one envelope: zero or more replies, followed by exactly one final envelope with `turn_complete: true`. A turn that produced no visible reply (for example, one that was merged into another turn already in progress) still sends that final envelope alone, with `content: ""`, so your integration always sees a definite end to every accepted turn.

This webhook is not signed. If you need to verify a delivery came from Osvi, configure a custom header (e.g. a bearer token) as one of the agent's delivery headers and check for it on your endpoint.

A chat agent's reply doesn't always come back as the response to your request. Osvi delivers it to a URL you configure whenever there's no live request to answer inline:

* A turn accepted through [Send Message (Async)](/api-reference/endpoint/chat/inbound_async) — the reply arrives here instead of in the `202` response.
* Any operator-authored message sent through [Send Support / Control Command](/api-reference/endpoint/chat/support) — a `tell`, a plain support message, or a takeover reply.

<Note>
  A session's most recent messages decide where its replies go: if the customer has been messaging over a [chat WebSocket](/api-reference/chat-websocket) connection, replies are delivered as events on that socket instead of to this webhook. This webhook is used for sessions driven through the REST endpoints (`POST /v1/chat/inbound` / `/inbound/async`).
</Note>

## Configuring a delivery URL

Set this under the chat agent's **Settings** tab, in the **Delivery** card:

| Setting | Description |
| - | - |
| **Delivery URL** | Where replies and turn results are POSTed. Required before `POST /v1/chat/inbound/async` will accept turns for the agent — that endpoint refuses with `delivery_url_required` if none is set. |
| **Delivery headers** | Custom HTTP headers sent with every delivery — e.g. a bearer token your endpoint checks to verify the request came from Osvi. |

<Warning>
  Deliveries to this URL are **not signed**. There's no HMAC signature or shared secret built in — if you need to verify a request came from Osvi, check for a custom header you configured above.
</Warning>

You can't remove an active agent's delivery URL while it's still active — deactivate the agent first. This prevents replies the agent already owes a customer from having nowhere to go.

## The envelope

Every delivery is a `POST` with a JSON body describing one reply, or a turn's completion:

| Field | Description |
| - | - |
| `session_id` | The chat session this reply belongs to. |
| `agent_uuid` | The agent that produced it. |
| `customer_id` | Your identifier for the customer, when known. |
| `content` | The reply text. Empty (`""`) on a completion-only envelope. |
| `role` | Always `assistant`. |
| `actor_kind` | `ai` or `operator` — who authored the content. |
| `ts` | Unix timestamp (seconds). |
| `turn_id` | The turn this reply belongs to, when there is one. |
| `reply_index` | Zero-based position of this reply among the (possibly several) replies of its turn, when the reply was produced by a turn. Not every reply carries one — use delivery order, not this field, to sequence replies. |
| `idempotency_key` | Echoes the `idempotency_key` you sent on the originating request, when you sent one — use it to correlate this delivery with your original call. |
| `turn_complete` | `true` only on the final envelope of a turn (see below). |
| `mode`, `help_requested` | Session state at completion — sent alongside `turn_complete`. |

If the reply is an interactive message, the envelope also carries `id`, `type: "interaction"`, `created_at`, `interaction_id`, `tool_type`, `schema_version`, `presentation`, `interaction_status`, `interaction_revision`, and (once answered) `selected_option_id`/`response_payload` — see [Interactive messages](/api-reference/endpoint/chat/interactive_messages) for what those mean.

## How a turn ends

A turn can produce more than one reply. They arrive as separate envelopes, in delivery order, and are always followed by exactly one final envelope with `turn_complete: true` — that's your signal the turn is fully done and nothing more is coming for it.

A turn that produces no reply at all — most commonly because your message arrived while another turn for the same session was still running and got folded into it — still sends that final envelope on its own, with `content: ""` and `turn_complete: true`. Every turn you get accepted for is guaranteed exactly one completion signal, even one with nothing to say.

## Delivery, retries, and ordering

* Respond with any **2xx** status within **15 seconds** to acknowledge a delivery.
* A non-2xx response, a timeout, or a connection error is retried automatically — up to **6 attempts total**, spaced a few seconds apart at first and up to about a minute apart later. Design your endpoint to be safe to receive more than once (see `idempotency_key` above).
* If every attempt fails, Osvi stops retrying that delivery — there's no further automatic redelivery after the 6th attempt.
* Deliveries for a turn are sent in order and are not retried out of order; a retry of an earlier delivery is not sent again once a later one for the same turn has succeeded.
* Deactivating the agent, or removing its delivery URL, stops future delivery attempts — replies already in flight are not guaranteed to arrive after that point.


## OpenAPI

````yaml WEBHOOK /chat_delivery
openapi: 3.1.0
info:
  title: OSVI AI API
  description: REST API for the OSVI AI voice and chat platform
  license:
    name: MIT
  version: 1.0.0
servers:
  - url: https://api.osvi.ai
security:
  - ApiToken: []
paths: {}
components:
  securitySchemes:
    ApiToken:
      type: apiKey
      in: header
      name: API-Token
      description: >-
        16-character API token associated with your OSVI account. Find it in
        your dashboard under Settings → API.

````