ElevenLabs
@mnemix-ai/elevenlabs-kit is a real, published integration kit for ElevenLabs Agents: conversation-initiation enrichment on the inbound-phone path, an inline session-context builder for outbound / Register Call / SDK sessions, and HMAC-verified post-call memory write-back. It calls the same two frozen routes documented on Voice integration patterns — POST /v1/recall_and_enrich before the call, POST /v1/calls/end after — and nothing else. This is a Northsun-built, Northsun-maintained kit against ElevenLabs' documented webhook contract — not a native ElevenLabs feature or a certified ElevenLabs integration.
Install
npm install @mnemix-ai/elevenlabs-kit @mnemix-ai/client
Verified in a clean directory: @mnemix-ai/elevenlabs-kit@0.1.0's peer dependency on @mnemix-ai/client@^0.2.2 resolves against the published @mnemix-ai/client@0.2.2, install exits 0, and both packages import clean.
@mnemix-ai/*is today's published scope. If your integration will outlive a possible future move to@northsun-ai/*, pin the version above rather than trackinglatest.
Set your key
MNEMIX_KEY=sk_live_...
MNEMIX_BASE_URL=https://mcp.mnemix.ai # optional; this is the default
ELEVENLABS_INITIATION_SECRET=... # shared-secret header value, recommended
ELEVENLABS_POST_CALL_SECRET=... # HMAC signing secret from the ElevenLabs dashboard, recommended
MNEMIX_KEY is your Northsun API key — see Installation for how to get one. Set it as a platform secret (wrangler secret put MNEMIX_KEY on Cloudflare Workers, or your platform's equivalent), never as a plaintext environment value in a repo or client bundle.
Two ways a conversation starts, one kit
ElevenLabs Agents has four ways a conversation can begin, but only one gets an ElevenLabs-initiated webhook before the agent speaks:
- Path A — native-Twilio-imported inbound numbers, and inbound SIP-trunk calls. ElevenLabs calls your configured conversation-initiation webhook (ElevenLabs' docs also call this "Twilio personalization" or the "pre-call webhook") before the agent's first utterance.
handleElevenLabsConversationInitiation()answers this path. - Path B — native outbound calls, the advanced Register Call route on your own Twilio infrastructure, and SDK/widget session starts. There is no ElevenLabs-initiated hook here — your own code starts the session, so it fetches Northsun context itself, inline.
buildElevenLabsSessionContext()answers this path. - The web widget (Path C) is out of scope for v1 — there is no phone number, and Northsun recall is phone-keyed.
import { Mnemix } from "@mnemix-ai/client";
import { handleElevenLabsConversationInitiation, buildElevenLabsSessionContext } from "@mnemix-ai/elevenlabs-kit";
const mnemix = new Mnemix({ apiKey: env.MNEMIX_KEY, baseUrl: env.MNEMIX_BASE_URL });
const opts = {
mnemix,
initiationSharedSecret: env.ELEVENLABS_INITIATION_SECRET,
postCallWebhookSecret: env.ELEVENLABS_POST_CALL_SECRET,
};
// Path A — conversation-initiation webhook:
const ctx = await handleElevenLabsConversationInitiation(opts, payload, { receivedSecret, tone: "warm" });
return Response.json(ctx.client_data); // the literal ElevenLabs response envelope
// Path B — your own outbound call / register_call / SDK session start:
const outboundCtx = await buildElevenLabsSessionContext({ mnemix }, { phone_number: "+15551234567" });
See packages/elevenlabs-kit/examples/cloudflare-worker.ts for the full working file, or examples/express-server.ts / examples/nextjs-route.ts for the same two routes on other runtimes.
Wire the webhooks
In your ElevenLabs agent's dashboard configuration, set the conversation-initiation webhook and the post-call webhook:
Conversation-initiation webhook = https://your-worker.example.com/elevenlabs/conversation-initiation
Post-call webhook = https://your-worker.example.com/elevenlabs/post-call
Required agent configuration — do this or nothing renders. ElevenLabs overrides are opt-in per field, off by default. If the agent's dashboard config doesn't have "system prompt override" and "first message override" explicitly enabled, client_data.conversation_config_override is silently ignored — undetectable from this kit's side. See packages/elevenlabs-kit/examples/elevenlabs-agent-config.json for the exact dashboard fields.
What you get back
variables carries the same 13-field shape every sibling kit returns (caller_name, is_returning, history_known, last_intent, last_call_summary, carrier, line_type, company, role, industry, suggested_intent, memory_facts, memory_status), also projected into client_data.dynamic_variables (nulls coalesced to "", since ElevenLabs' dynamic_variables only support string | number | boolean). system_prompt is the same text embedded at client_data.conversation_config_override.agent.prompt.prompt. client_data is the literal wire body — return it directly from a Path A handler, or embed it in a Path B call.
memory_status is a tri-state, because memory_facts alone cannot tell "this caller genuinely has no governed facts" apart from "the facts read failed and we could not check":
"ok"— the read ran and answered.memory_factsis a real measurement, possibly empty."unavailable"— the read did not complete (server-side budget or error, or recall itself failed, timed out, or was never reached).memory_factsis[]here too, but that[]is not a measurement. The prompt builder renders an explicit instruction telling the agent not to state or imply the caller has no facts on file, instead of staying silent — silence would let the agent infer "nothing on file" on its own, which is the exact false statement this field exists to prevent."off"— your tenant deployment has memory-unification reads disabled. Feature off, not a failure; rendered the same as"ok"with an empty list.
Current limitation — read this before you branch on "ok". The tri-state is only as precise as what the recall API puts on the wire, and the server-side change that reports a dropped governed read distinctly is not yet released. Until it is:
"ok"under-warns. If the governed-facts read times out or errors for a caller who already has call history, the API still returns an empty list, and it is indistinguishable from a caller who genuinely has no stored facts. The kit reports"ok"and the prompt stays silent."unavailable"can over-warn. The kit infers it when the caller's interaction count is also unreadable, which is deliberate but is not proof the facts read itself failed. Do not diagnose a facts outage from this field alone.
The direction of both errors is deliberate: the kit would rather warn you about a record it did read than stay silent about one it did not. Treat "unavailable" as "do not assert this caller has nothing on file" rather than as a precise diagnostic, and do not treat "ok" as proof of a measurement until the server change ships. This note will be removed when it does.
history_known distinguishes "we checked and this caller has no history" from "we don't know" — is_returning alone can't express the second case. When the caller's total-call count is unavailable and no calls are cached, history_known is false so the prompt asks rather than wrongly announcing a first-time caller by name.
See What recall gives you back for what populates these fields and when a value is legitimately absent rather than wrong.
Webhook verification — two different mechanisms
Unlike vapi-kit/bland-kit (one HMAC gate each), this kit authenticates its two webhooks differently:
| Webhook | Mechanism |
|---|---|
| Conversation-initiation (pre-call) | Shared-secret request header, timing-safe compare |
Post-call (post_call_transcription) | HMAC-SHA256, elevenlabs-signature header, t=<unix_seconds>,v0=<hex> |
If postCallWebhookSecret is configured, handleElevenLabsPostCall() refuses ({ ok: false }) without calling Northsun when the signature is missing or invalid. If initiationSharedSecret is configured, handleElevenLabsConversationInitiation() falls back to a safe default context (never throws) on a mismatch — ElevenLabs can still start the call; the failure is logged, not surfaced to the caller.
The post-call HMAC algorithm was read directly out of the official @elevenlabs/elevenlabs-js SDK's shipped source, not guessed from prose — see packages/elevenlabs-kit/src/signature.ts's file header for the citation.
Closing the attribution loop
context_audit_id and the caller's own phone number are stashed as reserved, mnemix_-prefixed dynamic_variables at initiation and read back off the post-call payload's echoed dynamic_variables — resolving at the docs/schema level ElevenLabs' documented behavior that post_call_transcription echoes the initiation dynamic_variables back. A real inbound call against a deployed endpoint is still the falsifier that confirms this against live traffic, not just docs (see packages/elevenlabs-kit/README.md's "What's proven, and what's still owed").
Verify it
Call the ElevenLabs agent's connected number. Your worker logs should show one recall on the conversation-initiation webhook, and one POST /v1/calls/end when ElevenLabs' post-call webhook fires. See Hangup, and what happens next for what happens immediately after that call.
Next steps
- Twilio, Retell, LiveKit — the same lifecycle on other platforms.
- Voice integration patterns — the platform-neutral lifecycle this kit implements.
- Production readiness — the launch checklist before you point this at real traffic.