> This is a page from the ElevenLabs documentation. For a complete page index, fetch https://elevenlabs.io/docs/llms.txt. For the full documentation in a single file, fetch https://elevenlabs.io/docs/llms-full.txt.

# Canal personnalisé

## Présentation

Custom Channel connecte un système de messagerie externe à un agent ElevenLabs. Envoyez les messages des utilisateurs à un webhook ElevenLabs, puis recevez les réponses de l’agent sur votre propre endpoint HTTPS.

> **Warning**
>
> Custom Channel est en version alpha.

> **Warning**
>
> Custom Channel n’est pas disponible pour les agents ou les Workspaces utilisant le mode zéro rétention.

## Fonctionnalités

| Fonctionnalité                   | Prise en charge                                                    |
| -------------------------------- | ------------------------------------------------------------------ |
| Mode zéro rétention (ZRM)        | Non pris en charge, indisponible pour les Workspaces et agents ZRM |
| Pièces jointes dans les messages | Non prises en charge, les messages sont uniquement textuels        |

## Configuration

#### Ouvrir Custom Channel

Ouvrez votre agent, sélectionnez **Channels**, choisissez **Custom Channel**, puis cliquez sur **Add trigger**.

#### Configurer le déclencheur

Sélectionnez une connexion existante ou créez-en une, puis saisissez l’**URL du webhook de réponse**.

#### Copier les identifiants

Cliquez sur **Add**, puis copiez l’**URL du webhook entrant**, le **secret entrant** et le **secret de signature sortant**.

#### Configurer votre service

Envoyez les messages des utilisateurs à l’URL du webhook entrant avec le secret entrant dans `X-Webhook-Secret`. Utilisez le secret de signature sortant pour vérifier chaque réponse.

## Envoyer un message

Envoyez une requête `POST` à l’URL du webhook générée :

```text
POST /v1/convai/api-integrations/custom_channel/triggers/{trigger_connection_id}/async_message
X-Webhook-Secret: <inbound-secret>
Content-Type: application/json
```

```json
{
  "data": {
    "type": "user_message",
    "text": "Where is my order?",
    "user_identifier": "customer_8427"
  },
  "user_message_id": "msg_01k1e6z3f4t8n9c2",
  "dynamic_variables": {
    "order_id": "order_72491"
  }
}
```

| Champ                  | Obligatoire | Description                                                                                       |
| ---------------------- | ----------- | ------------------------------------------------------------------------------------------------- |
| `data.type`            | Oui         | Doit être `user_message`.                                                                         |
| `data.text`            | Oui         | Message utilisateur non vide.                                                                     |
| `data.user_identifier` | Non         | Identifiant de l’utilisateur externe.                                                             |
| `user_message_id`      | Oui         | Clé d’idempotence non vide fournie par votre système.                                             |
| `conversation_id`      | Non         | Incluez l’ID renvoyé pour poursuivre une conversation. Omettez-le pour en commencer une nouvelle. |
| `dynamic_variables`    | Non         | Variables dynamiques fournies à l’agent pour ce tour.                                             |

ElevenLabs renvoie `202 Accepted` avant de traiter le tour :

```json
{
  "conversation_id": "conv_01k1e72d4x8p6v3m",
  "status": "queued"
}
```

Pour poursuivre la conversation, envoyez une autre requête avec ce `conversation_id` et un nouveau `user_message_id`.

## Recevoir les réponses

ElevenLabs envoie une requête `POST` à l’URL du webhook de réponse après chaque tour :

```json
{
  "version": "1",
  "conversation_id": "conv_01k1e72d4x8p6v3m",
  "user_message_ids": ["msg_01k1e6z3f4t8n9c2"],
  "status": "completed",
  "data": [
    {
      "type": "agent_response",
      "event": {
        "agent_response": "Your order is scheduled to arrive tomorrow.",
        "response_id": "9f2c1a7e-4b3d-4e8a-9c1f-2d6b8e0a5f31",
        "event_id": 4
      }
    },
    {
      "type": "agent_tool_response",
      "event": {
        "tool_name": "end_call",
        "tool_call_id": "toolu_01k1e70r4b8y",
        "tool_type": "system",
        "event_id": 4,
        "is_called": true,
        "is_error": false,
        "is_blocked": false,
        "status": "success"
      }
    }
  ],
  "error": null
}
```

Si le traitement échoue, `status` est défini sur `failed`, `data` sur `[]` et `error` contient une description.

`data` répertorie les événements dans l’ordre des tours. Chaque élément comporte un `type` et un `event` :

* `agent_response` contient une réponse de l’agent. `response_id` identifie cette réponse de manière unique, tandis que `event_id` l’associe à un tour. Concaténez les valeurs `agent_response` si votre canal affiche une bulle de texte par tour.
* `agent_tool_response` signale le résultat d’un outil et partage l’`event_id` du tour. Son `status` est `success`, `error`, `blocked` ou `skipped`. Une réponse avec `tool_type: "system"`, `tool_name: "end_call"` et `status: "success"` signifie que l’agent a mis fin à la conversation.

Plusieurs messages entrants peuvent être regroupés en un seul tour. `user_message_ids` répertorie les ID des messages utilisateur auxquels cette réponse répond.

## Vérifier les signatures des réponses

Chaque réponse comprend un en-tête `ElevenLabs-Signature` :

```text
t=1753876800,v0=<hex-digest>
```

Le condensat est une signature HMAC-SHA256 sur `{timestamp}.{raw_request_body}` utilisant le secret de signature sortant. Vérifiez le corps brut avant d’analyser le JSON et rejetez les horodatages obsolètes.

```python
import hashlib
import hmac
import time


def verify_signature(raw_body: bytes, header: str, secret: str) -> None:
    values = dict(part.split("=", 1) for part in header.split(","))
    timestamp = values["t"]
    if abs(time.time() - int(timestamp)) > 30 * 60:
        raise ValueError("Stale webhook signature")

    expected = hmac.new(
        secret.encode(),
        timestamp.encode() + b"." + raw_body,
        hashlib.sha256,
    ).hexdigest()
    if not hmac.compare_digest(expected, values["v0"]):
        raise ValueError("Invalid webhook signature")
```

```typescript
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifySignature(rawBody: Buffer, header: string, secret: string): void {
  const values = Object.fromEntries(header.split(",").map((part) => part.split("=", 2)));
  const timestamp = values.t;
  if (!timestamp || Math.abs(Date.now() / 1000 - Number(timestamp)) > 30 * 60) {
    throw new Error("Stale webhook signature");
  }

  const expected = createHmac("sha256", secret).update(`${timestamp}.`).update(rawBody).digest();
  const received = Buffer.from(values.v0 ?? "", "hex");
  if (received.length !== expected.length || !timingSafeEqual(expected, received)) {
    throw new Error("Invalid webhook signature");
  }
}
```

## Comportement de livraison

ElevenLabs effectue trois tentatives de livraison en cours de traitement, environ à 0, 0,5 et 2 secondes. Une réponse `2xx` confirme que la livraison a réussi.

La taille des corps de requête est limitée à 256 KiB.