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

# Attach Audio After a Call

> Submit transcripts immediately at call end and attach audio when the recording URL finalizes — designed for telephony stacks (Twilio, AWS Connect, Vonage) where the recording URL is asynchronously generated.

## Overview

Most telephony stacks finalize the recording URL **after** the call has ended — typically 30 to 90 seconds later, well past the point where many agent containers have been recycled or scaled in. That makes it unreliable to submit transcript and audio together in a single, atomic post-call call.

The solution is a two-call pattern. At call end, you `POST /v1/conversations:submit` with the transcript and metadata. You receive a `conversation_id` back synchronously. Once your telephony platform tells you the recording URL is finalized, you `PATCH /v1/conversations/{conversation_id}` to attach it.

The benefit is that text-only metrics — anything derived from the transcript — start running the moment the conversation is submitted. Audio-derived metrics run as a second wave, once the recording is attached.

## The two-call flow

<Steps>
  <Step title="Submit transcript at call end">
    `POST /v1/conversations:submit` accepts the transcript and returns a `conversation_id` synchronously. Text-only metrics begin running immediately. The conversation enters `IN_PROGRESS` status.

    ```bash theme={null}
    curl -X POST https://api.coval.dev/v1/conversations:submit \
      -H "x-api-key: $COVAL_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "transcript": [
          {"role": "agent", "content": "Hello, how can I help you today?"},
          {"role": "user", "content": "I need to check my order status."}
        ],
        "agent_id": "your-agent-id",
        "external_conversation_id": "CA1234567890abcdef",
        "occurred_at": "2026-05-13T18:42:00Z",
        "metadata": {
          "channel": "twilio-pstn",
          "from_number": "+14155550100"
        }
      }'
    ```

    Response:

    ```json theme={null}
    {
      "conversation_id": "conv_01HXYZ...",
      "status": "IN_PROGRESS",
      "has_audio": false
    }
    ```

    Store the returned `conversation_id`. You will need it for the PATCH call.
  </Step>

  <Step title="Attach the recording URL when ready">
    Once the recording URL finalizes (your telephony stack will deliver this via a webhook or callback), `PATCH /v1/conversations/{conversation_id}` with `audio_url`. Audio-derived metrics now run as a second wave.

    ```bash theme={null}
    curl -X PATCH "https://api.coval.dev/v1/conversations/$CONVERSATION_ID" \
      -H "x-api-key: $COVAL_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "audio_url": "https://api.twilio.com/2010-04-01/Accounts/AC.../Recordings/RE....wav"
      }'
    ```

    Response:

    ```json theme={null}
    {
      "conversation_id": "conv_01HXYZ...",
      "status": "IN_PROGRESS",
      "has_audio": true
    }
    ```

    See the [full PATCH conversation API reference](/api-reference/v1/conversations/conversations/attach-audio-to-a-conversation) for all supported fields. You can also attach inline base64 audio via the `audio` field rather than a remote URL.
  </Step>
</Steps>

<Accordion title="Show full submit request body">
  ```json theme={null}
  {
    "transcript": [
      {"role": "agent", "content": "Hello, how can I help you today?"},
      {"role": "user", "content": "I need to check my order status."},
      {"role": "agent", "content": "I can help with that. Can I have your order number?"},
      {"role": "user", "content": "It is 12345."}
    ],
    "agent_id": "your-agent-id",
    "external_conversation_id": "CA1234567890abcdef",
    "occurred_at": "2026-05-13T18:42:00Z",
    "metadata": {
      "channel": "twilio-pstn",
      "from_number": "+14155550100",
      "to_number": "+18005551212",
      "tenant_id": "acme-corp"
    }
  }
  ```
</Accordion>

## Webhook timing — two waves

<Note>
  **Configure your webhook consumer to expect two waves.**

  * **Wave 1 (text-only metrics)** — fires within seconds of the `POST /v1/conversations:submit` returning. Carries everything derived from the transcript.
  * **Wave 2 (audio-derived metrics)** — fires only after the `PATCH /v1/conversations/{conversation_id}` lands and the recording is processed. Carries metrics like STT WER, audio sentiment, and diarization-dependent scores.

  Dedupe by `external_conversation_id` if you store webhook payloads downstream — the same conversation will be referenced by both waves.
</Note>

## Idempotency

Audio can be attached **once** per conversation. The PATCH call is single-shot.

* A second PATCH to the same `conversation_id` returns `409 ALREADY_EXISTS`.
* A conversation submitted with audio inline via `POST /v1/conversations:submit` (passing `audio_url` or `audio` in the initial body) already has audio attached and cannot be PATCHed.
* To re-run with a corrected recording, submit a new conversation with a new `external_conversation_id`.

<Warning>
  If your recording-status webhook fires more than once for the same call — some providers retry on transient receiver errors — your handler must be idempotent against the 409. Treat the first 200 as success and ignore subsequent 409s.
</Warning>

## When you don't need this

<Tip>
  **Skip the PATCH step if either of the following applies:**

  * **You control the media path.** If your stack hands you the finalized recording URL or bytes at the moment the call ends — for example, you run your own media server and write the file before the agent disconnects — just call `POST /v1/conversations:submit` with `audio_url` (or `audio`) in the same payload. There is nothing to PATCH later.
  * **You only need transcript-derived metrics.** Submit the transcript with `POST /v1/conversations:submit` and stop there. The conversation will be evaluated against any metric that does not require audio, and `has_audio` will remain `false`.
</Tip>

## See also

* [Live Conversations](/concepts/conversations/overview) — the parent concept page
* [Submit a conversation for evaluation](/api-reference/v1/conversations/conversations/submit-conversation-for-evaluation) — the `POST /v1/conversations:submit` API reference
* [Attach audio to a conversation](/api-reference/v1/conversations/conversations/attach-audio-to-a-conversation) — the `PATCH /v1/conversations/{conversation_id}` API reference
* [Uploading Conversations](/concepts/conversations/uploading) — transcript and audio formats for upload
* [Trace Search](/concepts/conversations/trace-search) — search across traced conversations once they are submitted
