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

# ElevenLabs Agents Platform

> The three webhooks of an ElevenLabs phone agent, answered from your server: conversation initiation, tools and post-call.

A call to an ElevenLabs agent reaches your server through three webhooks, and the adapter answers each: the **conversation initiation** one hands over the context as a dynamic variable, the **server tools** run the history kit for whoever is on the line, and the **post-call** one records the transcript turn by turn and ends the conversation. The integration is on the server side, not the device: in a contact center, the call starts on a phone.

## Install

<CodeGroup>
  ```sh Python theme={null}
  pip install 'niadra[elevenlabs]'   # no framework dependency: the handlers take the body and the headers
  ```

  ```sh TypeScript theme={null}
  npm install @niadra/sdk   # @niadra/sdk/elevenlabs needs no ElevenLabs package
  ```
</CodeGroup>

The TypeScript integration ships with `@niadra/sdk` 0.3.0, ready on `main` and on npm when it is published; until then the npm package is 0.1.1.

## The five primitives

| Primitive    | How the adapter wires it                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Context      | The initiation webhook ("Fetch initiation client data from a webhook") brings `caller_id`, `called_number`, `agent_id`, `call_sid` and `conversation_id`; the adapter opens the conversation, reads `context(view="voice")` and answers `conversation_initiation_client_data` with the dynamic variable `niadra_context`. Put `{{niadra_context}}` in the agent's prompt, after your instructions (in TypeScript, also `{{niadra_turn}}` where other channels' turns should go) |
| Turns        | The `post_call_transcription` webhook checks `ElevenLabs-Signature` (HMAC-SHA256 of `"<t>.<body>"`, 30 minutes of tolerance) and records each item of `transcript[]` as a turn at its moment in the call, with the model's usage; then `end()`. Idempotency keys come from the call, so a redelivered webhook records nothing twice                                                                                                                                             |
| Tools        | `tool_configs(url)` (Python) and `toolConfigs()` (TypeScript) produce the three history tools as ElevenLabs webhook tools, with the same names, descriptions and parameters as every kit. The call's identifiers (`system__call_sid`, `system__conversation_id`, `system__caller_id`) are filled by ElevenLabs, never by the model, and the handler binds the kit to that caller                                                                                                |
| Verification | The initiation webhook calls `verify()` when you pass the carrier's attestation; without it, the read is at V0                                                                                                                                                                                                                                                                                                                                                                  |
| Handoff      | `transfer_to_agent` and `transfer_to_number` arrive in the post-call webhook and become a `handoff`                                                                                                                                                                                                                                                                                                                                                                             |

The Niadra conversation id is the call's `call_sid` (else ElevenLabs' `conversation_id`), the same in all three webhooks. The subject is the caller's number; pass `subject=` (a function of the call's variables) for callers identified otherwise.

## Minimal example

<CodeGroup>
  ```python Python theme={null}
  """The server side of an ElevenLabs phone agent: initiation, tools and post-call webhooks.

  Run: uvicorn elevenlabs_server:app. In the ElevenLabs agent, set the initiation webhook to
  /elevenlabs/initiation, add the tools from tool_configs(), and the post-call webhook to
  /elevenlabs/post-call. Put {{niadra_agent_memory}} and {{niadra_context}} in the system prompt.
  """

  import os

  from fastapi import FastAPI, Request, Response

  from niadra import AsyncNiadra
  from niadra.integrations.elevenlabs import ElevenLabsWebhooks, tool_configs

  niadra = AsyncNiadra(channel="voice")
  hooks = ElevenLabsWebhooks(
      niadra,
      webhook_secret=os.environ["ELEVENLABS_WEBHOOK_SECRET"],
      shared_secret=os.environ["NIADRA_TOOL_SECRET"],
      agent_memory=True,
  )
  app = FastAPI()
  TOOLS = tool_configs("https://agent.example.com/elevenlabs/tools", secret=os.environ["NIADRA_TOOL_SECRET"])


  def answer(result) -> Response:
      return Response(result.text(), result.status, media_type=result.content_type)


  @app.post("/elevenlabs/initiation")
  async def initiation(request: Request) -> Response:
      return answer(await hooks.conversation_initiation(await request.body(), request.headers))


  @app.post("/elevenlabs/tools/{name}")
  async def tool(name: str, request: Request) -> Response:
      return answer(await hooks.server_tool(name, await request.body(), request.headers))


  @app.post("/elevenlabs/post-call")
  async def post_call(request: Request) -> Response:
      return answer(await hooks.post_call(await request.body(), request.headers))
  ```

  ```typescript TypeScript theme={null}
  // The three ElevenLabs webhooks on Hono, for any runtime Hono runs on (Node, Bun, Deno, Workers).
  import { Hono } from "hono";
  import { Niadra } from "@niadra/sdk";
  import { elevenLabs } from "@niadra/sdk/elevenlabs";

  const niadra = new Niadra();
  const handlers = elevenLabs({
    niadra,
    secret: process.env.NIADRA_ELEVENLABS_SECRET ?? "",
    webhookSecret: process.env.ELEVENLABS_WEBHOOK_SECRET ?? "",
  });

  export const app = new Hono();

  app.post("/elevenlabs/initiation", async (c) => {
    const { status, body } = await handlers.initiation(await c.req.json(), c.req.raw.headers);
    return c.json(body, status as 200);
  });

  app.post("/elevenlabs/tools", async (c) => {
    const { status, body } = await handlers.tool(await c.req.json(), c.req.raw.headers);
    return c.json(body, status as 200);
  });

  app.post("/elevenlabs/post-call", async (c) => {
    // The raw body: the signature covers the exact bytes ElevenLabs sent.
    const { status, body } = await handlers.postCall(await c.req.text(), c.req.raw.headers);
    return c.json(body, status as 200);
  });

  // The server tool configurations to create in ElevenLabs (API or dashboard), printed once.
  if (process.argv.includes("--print-tools")) {
    console.log(JSON.stringify(handlers.toolConfigs({ url: "https://api.acme.com/elevenlabs/tools", secretId: "YOUR_SECRET_ID" }), null, 2));
  }

  export default app;
  ```
</CodeGroup>

The handlers are plain functions of the body and the headers: they fit FastAPI, Flask, Django, Hono, a Lambda or a Worker. The same code is in `examples/elevenlabs_server.py` and `examples/elevenlabs-hono.ts`.

## Agent memory

With `agent_memory=True`, the agent's own notes arrive in the `niadra_agent_memory` variable; put `{{niadra_agent_memory}}` right before `{{niadra_context}}` in the agent's prompt. See [Agent memory](/en/concepts/agent-memory).

## Limits

* The initiation webhook and the server tools return customer data, so they require the `X-Niadra-Secret` header you configure in ElevenLabs (`shared_secret`); without it, 401.
* Niadra slow or down never fails the call: the initiation answers an empty context, a tool answers that the history is unavailable, and the post-call webhook still answers 200.
* The carrier's attestation does not come in ElevenLabs' webhook; without it, the read is at V0.
* Tested with recorded payloads in the public format of the three webhooks and signatures computed in the test itself; no ElevenLabs account is needed.

## Next steps

<CardGroup cols={2}>
  <Card title="Voice agents" href="/en/guides/voice-agents">
    what the call proves and what the policy releases.
  </Card>

  <Card title="Vapi" href="/en/integrations/vapi">
    the same design, on one server URL.
  </Card>
</CardGroup>
