Add an example for watching calls from your own call-start webhook - #2
Merged
Merged
Conversation
A customer who already receives ElevenLabs conversation-initiation webhooks had no worked path from that event to a watched call. main.py serve in the elevenlabs example is the shape of it, but it returns no dynamic_variables, so it breaks any agent whose prompt uses them, and it prints one line per call, which is not enough to see what the SDK did. This is the same flow as a self-contained example: take the conversation_id out of the webhook, hand it to Monitor.watch, and the monitor socket is held by the customer process with the customer key. Every step logs, so a run can be read afterwards: the raw webhook body, the reply handed back at call setup, the socket opening, each turn, each analysis with its findings, and each nudge delivered back into the live conversation. The README is four steps and carries real output from a real inbound phone call. Step two is the part a correct-looking setup gets wrong: monitoring_enabled and enable_conversation_initiation_client_data_from_webhook are both off by default and neither is discoverable from either product's screen, so an agent missing the second one produces calls whose webhook is never called. Two limits are written down rather than left to be discovered. A contextual update shapes the next turn instead of interrupting, and ElevenLabs acknowledges nothing when one is delivered, so the README gives the canary trick for anyone who needs proof in their own setup. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Devin Review found 5 potential issues.
3 bugs not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)
| if conversation_id and monitor is not None: | ||
| await monitor.watch(conversation_id, user=User(id=str(caller), role="MEMBER")) | ||
| log("WATCH", f"SDK now watching {conversation_id}") | ||
| task = monitor._watching.get(conversation_id) |
Contributor
| ```python | ||
| @app.post("/calls") | ||
| async def call_started(body: dict): | ||
| await monitor.watch(body["conversation_id"], user=User(id=body["caller_id"])) |
Contributor
Comment on lines
+181
to
+182
| @app.post("/calls") | ||
| async def call_started(request: Request) -> dict[str, Any]: |
Contributor
Comment on lines
+225
to
+227
| @app.get("/health") | ||
| def health() -> dict[str, Any]: | ||
| return {"ok": True, "watching": list((monitor._watching if monitor else {}).keys())} |
Contributor
Comment on lines
+190
to
+195
| log("", f" {json.dumps(payload)[:900]}") | ||
|
|
||
| conversation_id = str(payload.get("conversation_id") or "").strip() | ||
| agent_id = str(payload.get("agent_id") or "").strip() | ||
| caller = payload.get("caller_id") or payload.get("called_number") or "unknown" | ||
| log("WEBHOOK", f"conversation={conversation_id or '(none)'} agent={agent_id or '(none)'} caller={caller}") |
Contributor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
examples/elevenlabs-webhook/, the path a customer actually has: they alreadyreceive ElevenLabs' conversation-initiation webhook at a URL they own, and they want that
event to start a watched call. Their process holds the monitor socket with their
ElevenLabs key, and DeepTrust never connects to ElevenLabs.
The integration is three lines, and the README opens with them:
Why not just point at
main.py serveservein the elevenlabs example is the right shape but not usable as written. Itreturns no
dynamic_variables, so any agent whose prompt interpolates variables renderswith unresolved placeholders. It logs one line per call, which is not enough to tell a
delivered nudge from a dropped one. And the
tunnel.shbeside it launches alive.pythat does not exist, on a different port from the one
servebinds.What is in it
Everything the run does is logged: the raw webhook body, the reply handed back at call
setup, the socket opening, every turn appended, every analysis with its findings, and
every nudge delivered back into the live conversation. The socket and the client are
wrapped only for logging, through the public
connectandon_analysisarguments, so theexample exercises the shipped
Monitorrather than a variant of it.The README
Four steps: install and set keys, turn on the two agent switches, run the receiver and
expose it, point ElevenLabs at it.
Step two is the one a correct-looking setup gets wrong.
monitoring_enabledandenable_conversation_initiation_client_data_from_webhookare both off by default, neitheris discoverable from either product's screen, and an agent missing the second one takes
calls whose webhook is never called, not even to be rejected. Both are set in one PATCH.
Two limits are stated rather than left to be discovered:
adapter, repeated here because it is what a reader of this example will expect to see.
appear in the conversation record afterwards. The README gives the canary trick for
anyone who needs proof in their own setup.
Verified
Run end to end against a real inbound phone call: ElevenLabs called the receiver, the
socket opened 232 ms later before the agent's first word, 9 caller turns and 10 agent
turns were captured, four analyses returned
risk=high, and the nudge was delivered onthe same socket each time. The log in the README is that run.
Also verified from the new directory:
uv syncresolves the local SDK, the receiver bootsclean, and it answers through a public tunnel.
Not touched
The root
README.mdand the existingexamples/elevenlabs/example are unchanged. Theonly edit outside the new directory is one row in the
examples/README.mdindex.🤖 Generated with Claude Code