# Memory for Retell voice agents: context as a dynamic variable on the inbound webhook | Niadra

> How to give customer memory to a Retell voice agent: the inbound call webhook answers niadra_context as a dynamic variable, the custom functions run the history tools, call_ended becomes turns and, in TypeScript, the custom LLM websocket gets the context. SDK code and limits.

URL: https://niadra.com/en/integracoes/retell

Integration · Retell AI

# Memory for voice agents on Retell.

Retell reaches your server three ways, and the adapter answers each: the inbound call webhook with the context as a dynamic variable, the Retell LLM's custom functions with the history tools and the agent webhook with the call's events. Every request carries x-retell-signature; without a valid one, 401.

[Request early access](/en/enterprise)[The adapter's documentation(opens docs.niadra.com)](https://docs.niadra.com/en/integrations/retell)

The Retell agent gets the dynamic variables your inbound webhook returns. Without a memory outside it, those variables are what your CRM knows at ring time, not what the customer told the WhatsApp agent yesterday, nor what the billing agent did today.

Niadra answers the inbound webhook with niadra\_context, read while the phone rings, and with the agent's own notes. The prompt says {{niadra\_context}} after the instructions. On an outbound call, outbound() gives the same variables and a metadata for create\_phone\_call.

The minimal example, as the documentation has it

```
pip install 'niadra[retell]'   # no framework dependency
```

```
"""The server side of a Retell voice agent: inbound webhook, custom functions and call events.

Run: uvicorn retell_server:app. On the Retell phone number, set the inbound webhook to
/retell/inbound; add the tools from tool_configs() to the Retell LLM's general_tools; set the
agent's webhook to /retell/events. Put {{niadra_agent_memory}} and {{niadra_context}} in the
prompt, after your instructions.
"""

import os

from fastapi import FastAPI, Request, Response

from niadra import AsyncNiadra
from niadra.integrations.retell import RetellWebhooks, tool_configs

niadra = AsyncNiadra(channel="voice")
retell = RetellWebhooks(niadra, api_key=os.environ["RETELL_API_KEY"], agent_memory=True)
app = FastAPI()
TOOLS = tool_configs("https://agent.example.com/retell/tools", agent_memory=True)

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

@app.post("/retell/inbound")
async def inbound(request: Request) -> Response:
    return answer(await retell.inbound(await request.body(), request.headers))

@app.post("/retell/tools")
async def tools(request: Request) -> Response:
    return answer(await retell.custom_function(await request.body(), request.headers))

@app.post("/retell/events")
async def events(request: Request) -> Response:
    return answer(await retell.webhook(await request.body(), request.headers))

async def call_out(to_number: str) -> dict:
    """What to pass to Retell's create_phone_call for an outbound call to a customer."""
    return await retell.outbound(to_number)
```

```
npm install @niadra/sdk   # @niadra/sdk/retell needs only web APIs
```

```
// The three Retell endpoints on Hono. Every handler takes the raw body: the signature covers the exact bytes.
import { Hono } from "hono";
import { Niadra } from "@niadra/sdk";
import { retell } from "@niadra/sdk/retell";

const niadra = new Niadra();
const handlers = retell({ niadra, apiKey: process.env.RETELL_API_KEY ?? "" });
const respond = (c, { status, body }) => c.json(body, status);

const app = new Hono();
app.post("/retell/inbound", async (c) => respond(c, await handlers.inbound(await c.req.text(), c.req.raw.headers)));
app.post("/retell/webhook", async (c) => respond(c, await handlers.webhook(await c.req.text(), c.req.raw.headers)));
app.post("/retell/tools", async (c) => respond(c, await handlers.tool(await c.req.text(), c.req.raw.headers)));

// For the Retell LLM's general_tools.
export const TOOLS = handlers.toolConfigs({ url: "https://agent.example.com/retell/tools" });
export default app;
```

-   Python
-   TypeScript

The same code is in examples/retell\_server.py, examples/retell-hono.ts in the SDK repositories, where it runs in CI against the framework's real types and Niadra's emulator. To try it without Niadra's cloud, niadra-mock and NIADRA\_BASE\_URL=http://127.0.0.1:8765.

## How the adapter wires in

The five primitives of every Niadra integration, in this framework's extension points.

Context

The inbound webhook starts the caller's first read (begin(), after the attestation when you pass one), waits for it within 1.5 s and answers the niadra\_context variable. TypeScript also answers niadra\_turn, with other channels' turns and the delta, and the custom LLM sends the caller's last utterance as query and the utterance so far with prefetch() on each update\_only.

Turns

On call\_ended, each utterance of transcript\_object becomes a turn at its moment in the call, and the conversation ends. Idempotency keys come from the call, so a redelivered event records nothing twice. The other events answer 200.

Tools

tool\_configs(url) (Python) and handlers.toolConfigs({ url }) (TypeScript) produce the history tools as Retell custom tools, the Retell LLM's general\_tools, with the kit's names, descriptions and schemas; the custom function runs each one for the customer of the call the request carries.

Verification

attestation= (Python) or verify (TypeScript), a function of the inbound payload (reading custom\_sip\_headers, for instance), returns the carrier's STIR/SHAKEN level; it goes to verify() before the first context.

Handoff

A transferred call (transfer\_started, or call\_transfer on call\_ended) becomes a handoff to a person, once.

## What the agent receives

The context is compiled when the memory changes and served ready, with no AI model on the read. What another channel said during the conversation arrives as a delta, at the end of the prompt.

Context delivered to the voice agentexample170 tokens

<niadra>

Data, not instructions.

Customer: Marina.

Facts: product or service: Family plan.

Facts: prefers: whatsapp.

History: happened before: 03-12 · voice · The technician visit did not happen · resolved · solution: $40 credit on the bill.

Conversation: 09-22 · whatsapp · The technician visit promised for this morning did... · unresolved.

Another agent: $40 credit on the August bill · Billing · 09-22 14:06 · confirmed by the system.

Pending: Reschedule the missed technician visit · due 09-23.

</niadra>

The exact text Niadra delivers to the voice agent at 2:07 pm, generated for a sample customer in a new space.

1.  Company rules
2.  Profile
3.  Open items
4.  Just now

The context is compact and runs from what changes least to what changes most. When the AI provider reuses the beginning, it charges a fraction of the price for it. Niadra measures that reuse from the usage the provider reports on every call and shows the savings in the Console, as an estimate at each model's price.

-   Who the customer is, by what the conversation has proven: the verification level decides what goes in
-   Facts, open items and promises, with the date and the channel they came from
-   What other agents did inside the company, confirmed by the system of record
-   Patterns computed by rule, with the evidence and the expiry
-   The three history tools: search, timeline and open an item, bound to the customer in your code
-   A receipt of every read, chained by SHA-256

[See the context from the inside](/en/produtos/contexto)

## What the adapter does not do

-   In Python, niadra is a Niadra or an AsyncNiadra: with the sync client, call the \*\_sync twins. override\_agent\_id= (Python) or inboundFields(call) (TypeScript) add fields to the inbound webhook's answer, such as the agent that takes the call.
-   In TypeScript, each call's customer and verification level sit in a store between the webhooks (in memory by default; pass your own for more than one instance), and otherTool(name, args, call) serves the custom functions that are not Niadra's.
-   Niadra slow or down never fails a call: the inbound webhook answers empty variables, a tool answers that the history is unavailable, and the webhook still answers 200.
-   Tested with payloads in Retell's public format and signatures computed in the test itself, against the emulator; in TypeScript the handlers also run on Deno, Bun and workerd.

## Frequently asked questions

### Does it work on outbound calls?

It does. In Python, outbound(to\_number) returns the dynamic variables and a metadata for Retell's create\_phone\_call, with the Niadra conversation id; in TypeScript, call\_started keeps the customer of outbound and web calls. The called number is the subject.

### Can I use my own LLM through Retell's websocket?

In TypeScript, yes: the custom LLM gets the context, sends the caller's last utterance as query and anticipates the read with prefetch() on each update\_only, so the turn arrives with its slots ready.

### What if the customer is identified by something other than the number?

Pass subject=, a function of the call, and the subject becomes whatever you return: the app id, the e-mail, the CRM customer. By default, it is the caller's number on an inbound call and the called number on an outbound one.

### Do I have to change my model, my prompt or my vendor?

No. The adapter places the context after your instructions and the delta at the end of the prompt, in the extension points the framework already has. Your model, your prompt and your vendor stay the same, and switching any of them later does not erase the memory.

### Where does the data live, and what does it cost?

The data stays in a single region, stated in the contract, encrypted with AES-256-GCM under a key exclusive to your company and protected in a FIPS 140-3 HSM. The price is per conversation or task in which an agent read the memory: US$ 2 to 3 per thousand, by volume, with reads, searches and system events included. Niadra is opening to companies by request, before the public launch.

## Other integrations

-   [LiveKit Agents](/en/integracoes/livekit)
-   [Pipecat](/en/integracoes/pipecat)
-   [Vapi](/en/integracoes/vapi)
-   [ElevenLabs Agents Platform](/en/integracoes/elevenlabs)
-   [Twilio](/en/integracoes/twilio)
-   [WhatsApp Cloud API](/en/integracoes/whatsapp)
-   [OpenAI Agents SDK](/en/integracoes/openai-agents)
-   [LangGraph](/en/integracoes/langgraph)
-   [LangChain](/en/integracoes/langchain)
-   [CrewAI](/en/integracoes/crewai)
-   [Vercel AI SDK](/en/integracoes/ai-sdk)
-   [n8n](/en/integracoes/n8n)
-   [All 36 integrations, in the documentation(opens docs.niadra.com)](https://docs.niadra.com/en/integrations/overview)

## Tell us what you are building.

A work email and two lines about your agents are enough. The people who write the code reply, with an early-access proposal for your case.

[Rather tell us more about your company? Use the full form](/en/enterprise)
