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

# Live agent handoff events

> Translate support-platform events into Voiceflow’s handoff events and send them to the runtime’s POST /handoff/webhook/custom route.

## What these events do

Your service translates support-platform events into Voiceflow’s handoff events. Send them to the runtime’s `POST /handoff/webhook/custom` route. Use the runtime base URL for your deployment; for the hosted runtime this is `https://general-runtime.voiceflow.com/handoff/webhook/custom`.

## Event envelope and payload

Every request contains `projectId` and `events`. Send one event per request. The parser accepts 1–50 events; single-event delivery makes retries and failures easier to isolate.

Every event requires a stable `id`, `conversationId`, `timestamp` string, and `type`. Use the conversation ID returned by your connection service.

| Type | Additional fields | Meaning |
| --- | --- | --- |
| `agent.joined` | `agent` with required `name`, optional `id` and `avatarUrl`; optional `message` | A human has joined |
| `message.received` | `agent`, non-empty `text` | A customer-visible human reply |
| `file.received` | `agent`, `file.url`; optional text and file name/contentType/size | A human attachment |
| `conversation.ended` | Optional `message` | The human conversation has ended |

Internal notes and echoed customer messages must not become `message.received`. Files must be retrievable without a vendor credential. The current custom event vocabulary does not include typing or queue-position events.

## Example human reply

```json
{
  "projectId": "YOUR_PROJECT_ID",
  "events": [
    {
      "id": "provider-event-123",
      "conversationId": "YOUR_CONNECTION_CONVERSATION_ID",
      "timestamp": "2026-10-18T12:00:00Z",
      "type": "message.received",
      "agent": {
        "id": "agent-1",
        "name": "Sam"
      },
      "text": "I can help with that order."
    }
  ]
}
```

Replace IDs and timestamp with values from the active conversation. Do not send this historical timestamp as a live signature timestamp.

## Sign, deliver and retry

Set `X-Signature-Timestamp` to the current Unix timestamp in **seconds**. Compute HMAC-SHA256 over the timestamp string concatenated directly with `JSON.stringify(body)`, encode the digest as base64, and set `X-Signature: primary=DIGEST`. Use the same Webhook secret configured in Voiceflow; it must be at least 32 characters.

The runtime validates within a five-minute clock window. It verifies serialized parsed JSON, not the raw request bytes: build and send one JSON object consistently. Rotation can include a `secondary=DIGEST` signature.

The [starter signing helper](/docs/examples/live-agent-handoff/protocol.mjs) implements this format. Retain the event ID across retries, refresh the signature timestamp, and deliver from a durable outbox. A 401 requires fixing signing; a 404 can mean the conversation is not active or creation has not completed. Retry an early callback only while creation is known to be pending; stop replaying events for closed sessions. A 2xx response acknowledges processing, not proof the customer has read the message.

## Related reference

[Connection API](/docs/developers/handoff/connection-api) · [Troubleshooting](/docs/documentation/handoff/troubleshooting)
