Home/Documentation
Retell AI

Retell × MemoAir Voice Memory

Drive memory from a Retell custom-function webhook. The shipped memoair-retell adapter handles memory search, optional turn persistence, verified webhook signatures, and call-end lifecycle.

First-class adapter available

Retell calls your public server, so this path is a networked_webhook. It does not claim the local sub-10ms SLO. Provider turn latency, webhook/network latency, and co-located memory-runtime latency are measured separately.

Install

terminal
BASH
pip install memoair-retell "fastapi>=0.110" "uvicorn>=0.27" python-dotenv

Environment

.env
BASH
MEMOAIR_API_KEY=memoair_pk_...
MEMOAIR_PROJECT_ID=proj_...
MEMOAIR_AGENT_ID=agent_...
RETELL_API_KEY=...

Configure the adapter

Org-only memory is the default. User lanes require org_only=False and an injected trusted user_resolver. The resolver must authenticate or look up an application-owned identifier; Retell call fields and custom metadata are never automatically trusted as MemoAir user IDs.

retell_adapter.py
PYTHON
import os
from typing import Any
from memoair_retell import MemoAirRetellHandler
 
 
async def user_resolver(payload: dict[str, Any]) -> str | None:
metadata = (payload.get("call") or {}).get("metadata") or {}
external_id = metadata.get("internal_customer_id")
if not external_id:
return None
# Your authenticated CRM lookup establishes the trust boundary.
return await crm.memoair_user_id_for(external_id)
 
 
handler = MemoAirRetellHandler(
api_key=os.environ["MEMOAIR_API_KEY"],
project_id=os.environ["MEMOAIR_PROJECT_ID"],
agent_id=os.environ["MEMOAIR_AGENT_ID"],
org_only=False,
user_resolver=user_resolver,
persist_transcript=True,
)

The reference FastAPI server in examples/retell/ verifies X-Retell-Signature against the exact raw body before parsing JSON.

Configure Retell

Add these custom functions / webhooks on your Retell agent:

  • search_memory POST https://your-server.com/retell/search_memory
  • record_turn POST https://your-server.com/retell/record_turn

The search_memory function should accept a required query string. Return contextText for Retell to feed back to the model as a system-message slot.

Production shape — many concurrent callers

A Retell bridge usually serves many concurrent callers from one or more processes. The adapter's default call-state store is process-local, TTL-bound, and capacity-bound. It is suitable for one worker; multi-worker deployments must inject a shared CallStateStore implementation with atomic end and transcript-pair claims, and run the trusted resolver on every webhook.

bridge_at_scale.py
PYTHON
handler = MemoAirRetellHandler(
api_key=os.environ["MEMOAIR_API_KEY"],
project_id=os.environ["MEMOAIR_PROJECT_ID"],
agent_id=os.environ["MEMOAIR_AGENT_ID"],
org_only=False,
user_resolver=user_resolver,
call_state_store=shared_call_state_store,
max_concurrent_users=64,
)

Transcript persistence uses deterministic pair-completion IDs in the shared state store. It does not claim that runtime or cloud turn_id values are idempotency keys. Processing or unavailable state returns a non-success response so Retell can retry.

See the multi-workspace SaaS guide for the partner / project-per-customer pattern when one Retell bridge fronts multiple customer workspaces.

Advanced

Need custom turn pairing, lane gating, or LLM-decided memory tools? See the advanced tool surface.