{"slug": "building-a-german-speaking-partner-with-elevenlabs-agents-and-cloudflare-workers", "title": "Building a German Speaking Partner with ElevenLabs Agents and Cloudflare Workers", "summary": "A developer built a German-language conversation partner using ElevenLabs voice agents and Cloudflare Workers, adding cross-session memory that ElevenLabs agents lacked at the time. The Cloudflare Worker (Hono) serves a Vite + TypeScript page, stores session data in D1, and exposes recall and store endpoints that the agent calls. The developer secured the setup by injecting an HMAC-signed user ID into the page via HTMLRewriter with a no-store cache header, and verifies that signature on every tool route and the post-call webhook so the browser cannot forge an identity.", "body_md": "Anyone who has learned a foreign language knows the gap between a passed exam and real fluency with native speakers. Closing it takes practice with someone at native level, and not everyone has a native-speaking partner or the budget and time for endless lessons.\n\nSo I asked myself: what if an AI voice agent could be that partner? Someone to have casual conversations with, like a buddy who speaks at native level, never loses patience, and points out your mistakes when they matter.\n\nI built it with ElevenLabs agents and Cloudflare Workers.\n\nAn ElevenLabs agent handles the voice, and a Cloudflare Worker (Hono) does everything else: it serves a small Vite + TypeScript page, stores data in D1, and exposes the endpoints the agent calls.\n\n`recall`, which returns what happened in recent sessions, and `store`, which saves items from the conversation.\n**Memory.** When I built this, ElevenLabs agents had no built-in memory across sessions, so I added it myself: the webhook stores each session, and the agent calls `recall` when a call starts. I load only the last two or three sessions. Pulling in more makes the agent sound robotic. Nobody remembers a hundred earlier conversations with a friend.\n\n**No secrets in the browser.** The Worker puts the user's identity into the page before it reaches the browser, as an HMAC-signed ID. The signing key only exists in the Worker.\n\n``` js\nconst rewritten = new HTMLRewriter()\n  .on('elevenlabs-convai', {\n    element(el) {\n      el.setAttribute(\n        'dynamic-variables',\n        JSON.stringify({ user_id: token, name: displayName }),\n      );\n    },\n  })\n  .transform(response);\n\nrewritten.headers.set('Cache-Control', 'no-store');\n```\n\nThe `no-store` header matters, because this page is personalized and must never be served from a shared cache.\n\n**The agent's endpoints are not public.** The agent never decides whose data it touches. The widget passes the signed user ID to the agent as a dynamic variable, and every tool call sends it back. The tool routes verify the signature before reading or writing anything, so an invented or altered ID is rejected. The post-call webhook has its own check. Route wiring, simplified:\n\n```\napp.get('/tools/recall', recallHandler);   // verifies the signed user_id first\napp.post('/tools/store', storeHandler);    // same check before any write\napp.post('/api/webhook', saveSession);     // webhook signature check\n```\n\nThe browser sees the signed ID but can't create a new one.\n\n**Routing.** Static files come straight from the asset bundle. Only `/`, `/api/*` and `/tools/*` invoke the Worker.\n\nIf you're building voice agents and want to know more about my agent - drop a comment, I'm happy to share details.", "url": "https://wpnews.pro/news/building-a-german-speaking-partner-with-elevenlabs-agents-and-cloudflare-workers", "canonical_source": "https://dev.to/asazhin/building-a-german-speaking-partner-with-elevenlabs-agents-and-cloudflare-workers-4fgj", "published_at": "2026-10-04 00:52:27+00:00", "updated_at": "2026-10-04 01:07:55.741669+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "ai-products", "developer-tools"], "entities": ["ElevenLabs", "Cloudflare Workers", "Hono", "Vite", "TypeScript", "Cloudflare D1"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/building-a-german-speaking-partner-with-elevenlabs-agents-and-cloudflare-workers", "markdown": "https://wpnews.pro/news/building-a-german-speaking-partner-with-elevenlabs-agents-and-cloudflare-workers.md", "text": "https://wpnews.pro/news/building-a-german-speaking-partner-with-elevenlabs-agents-and-cloudflare-workers.txt", "jsonld": "https://wpnews.pro/news/building-a-german-speaking-partner-with-elevenlabs-agents-and-cloudflare-workers.jsonld"}}