Skip to main content
Retell reaches your server three ways, and the adapter answers each: the inbound call webhook (call_inbound, set on the phone number), the Retell LLM’s custom functions and the agent webhook (call_started, transfer_started, call_ended, call_analyzed). Every request carries x-retell-signature (v=<unix ms>,d=<hex HMAC-SHA256 of body + ms>, keyed by the Retell API key that signs webhooks); without a valid one, within five minutes, the answer is 401. No Retell package is needed.

Install

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

The Niadra conversation id is Retell’s call_id (in Python, metadata.niadra_conversation_id when the call carries one, which outbound() sets). The subject is the customer’s number: the caller on an inbound call, the called number on an outbound one; pass subject= (a function of the call) for customers identified otherwise.

Minimal example

The Python code is in examples/retell_server.py in the SDK repository.

Custom LLM (websocket)

With a custom LLM (Retell’s LLM WebSocket), your server makes the model call. In TypeScript, handlers.llm(callId, { send, instructions }) is one session per websocket (/llm-websocket/:call_id): open() asks for the call details (and speaks your greeting), receive(event) answers the pings, records the utterances once a response is required and resolves to a turn whose messages carry your instructions, the agent’s notes, the pack and the call so far, with the turn_block at the end of the customer’s last utterance; turn.respond(text) sends the response (in chunks with { complete: false }, with endCall or transferNumber, which records the handoff). Utterances carry the same idempotency key on the websocket and in call_ended, so recording both ways stores each one once. Retell does not sign the websocket, so the session never takes the customer from the socket’s own call_details: it reads and records only for a call a signed webhook registered (inbound or call_started, in the store); until then the model gets its messages without context and nothing is recorded. trustCallDetails: true turns the old behavior back on, only for a socket that accepts Retell and no one else (an IP allowlist, a secret in its URL).
Python has no websocket session: open the conversation with the call_id and use the model SDK’s wrap() or the adapter of the framework you call; the webhooks above keep handling the context, the tools and the events.

Agent memory

RetellWebhooks(..., agent_memory=True) and tool_configs(url, agent_memory=True) (Python), or retell({ ..., agentMemory: true }) (TypeScript), bring the agent’s own notes in the variable niadra_agent_memory (put {{niadra_agent_memory}} right before {{niadra_context}}), before the pack in the custom LLM’s messages, and add search_agent_memory to the custom functions. See Agent memory.

Limits

  • 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, so one URL can serve them all.
  • 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.

Next steps

Vapi

the same design, with one server URL.

ElevenLabs

the initiation, tool and post-call webhooks.