Working prototype
A phone-based voice agent for Coinbase support, in two voices — grounded in what Coinbase already publishes, honest about what it doesn't, and never leaves a call empty-handed: unresolved issues become a real ticket, so the customer knows they were heard.
"What you're about to see is a working prototype — an actual phone number, not mockups. Two real scenarios, then I'll hand you the phone."
Most callers already tried the article and already called support. Nobody solved it, even after listening.
Few findings
CFPB · federal complaints
7,775 total, since 2011
Reddit — real posts on r/Coinbase
37 real posts found on Reddit
Fraud, withdrawal delays, account restrictions — the categories above — already have a real, documented fix. We voice it. We can't add self-service options or operations people. We can make sure no call disappears without a trace.
I tested this with real people, too — coworkers at NYC coworking spaces who'd hit these exact problems. All of them agreed on the same thing: our new Vapi IVA is faster, but it isn't actually more helpful until Coinbase resolves the issue behind the ticket it already opened.
Try it yourself
Same prompt, same knowledge, same backend — only the voice config differs, to show the assistant isn't married to one voice.
Register as a customer before calling to be addressed by name — same directory a real caller-ID lookup would check.
Register →Try it yourself — every topic
Every category below was actually researched against help.coinbase.com — 6 have a real page behind them, and 2 honestly don't. That absence is a finding, not a gap we didn't get to.
Account restrictions
DocumentedRead the article ↗Screenshotted — Demo 2+ closed-account withdrawal ↗
False-positive fraud flag
No fixNo page addresses "you flagged my real transaction" — confirmed absence, not unresearched.
Support unresponsive / no escalation
No fixCoinbase publishes no internal escalation channel — a ticket is the honest, useful thing this call can offer.
Demo 1 of 2 · Fraud & compromised account

help.coinbase.com — "Lock or unlock your compromised account" (real page, screenshotted live) Read the full article ↗
Real call — actual transcript, 2026-09-17
Caller"I think someone's gotten into my account — there's a withdrawal I never made."
JennyAI disclosureName personalization"I hear you, Vighnesh. That sounds really concerning. Before we go further, I want to be upfront — this call may be recorded, and you're talking with Jenny, our AI support assistant, not a live person. Is it okay if we keep going?"
Caller"Yes."
CallerTurn-taking"First, can you help me, like, how to lock?"
Jenny"Absolutely. In the Coinbase app, tap Account and Settings, then Security — there you'll see Lock Account."
···
CallerComplex email"It's V-I-G-H-N-E-S-H, b, b, h-u-r-i, at gmail dot com."
Jenny"Let me confirm — b as in bravo, b as in bravo — is that right?"
Caller"No — the two b's are actually d, for dog."
Jenny"Thanks for clarifying — d as in dog, d as in dog. Is that correct?"
···
Jenny"Your ticket is created, ID C-B dash 9-D-6-0-1-2-B-6. You're very welcome, Vighnesh."
Recording — actual call, 4:45
Recorded live through the deployed assistant, not staged.
coinbase
Support ticket confirmed
Created 2026-09-17 16:43:55 UTC
Ticket ID
CB-9D6012B6
Category
Fraud / scam
What you told us
Unauthorized transaction reported. Caller believes their account has been compromised and reports an unauthorized withdrawal. They have locked their account and need assistance with the suspicious transaction.
This is an automated confirmation from a prototype support assistant built for demo purposes — not an official Coinbase communication.
Demo 2 of 2 · Account restriction

help.coinbase.com — "Coinbase account restrictions" (real page, screenshotted live) Read the full article ↗
Real call — actual transcript, 2026-09-17
Caller"Hi, Jenny. I am not able to access my account."
JennyAI disclosure"I hear you can't access your account. Before we go further, I want to be upfront — this call may be recorded, and you're talking with Jenny, our AI support assistant, not a live person. Is it okay if we keep going?"
Caller"Yes."
···
CallerContext retention"Yeah — but can you give me a minute? There's someone at the door."
Jenny"Of course, take your time. Just let me know when you're ready to continue."
Caller"Okay, I'm back."
Jenny"It sounds like you might have been cut off there — can you tell me again what's happening with your account?"
Caller"No — it's restricted. I can't buy, I can't sell."
···
CallerComplex email"It's v, b, b, h, u, r, i, 1920, at gmail dot com."
Jenny"Let me confirm — v as in Victor, b as in bravo, b as in bravo — is that right?"
Caller"No — the b's are not b's, they should be d, as in dog."
Jenny"Got it — d as in dog, d as in dog. Is that correct?"
···
Jenny"I've created a support ticket for you. The ticket ID is C-B dash F-7-7-A-C-4-A-C."
Recording — actual call, 4:18
Recorded live through the deployed assistant, not staged.
coinbase
Support ticket confirmed
Created 2026-09-17 17:42:21 UTC
Ticket ID
CB-F77AC4AC
Category
Account restriction
What you told us
Account restricted - unable to buy or sell. Caller reports account restriction preventing buying and selling. Explained typical 10 business day review period and email notification. Caller requested support ticket for tracking.
This is an automated confirmation from a prototype support assistant built for demo purposes — not an official Coinbase communication.
$0.09 /min minimum
Vapi's own floor rate, confirmed on our account's billing dashboard. Scales up from there with model and voice choice.
<$0.001 /call
Our AWS backend — Lambda and DynamoDB, both pay-per-request, no idle cost. Email goes out through Resend. Fractions of a cent per call at this volume.
Call flow
The flow below is the "what." Every box on it was also a choice with a reason behind it — the cards under the diagram are the "why," for the six that weren't obvious.
Why raise startSpeakingPlan.waitSeconds to 1.0?
Default 0.4s cut callers off mid-sentence in live testing. Paired it with transcriptionEndpointingPlan: {onPunctuationSeconds: 0.2, onNoPunctuationSeconds: 2, onNumberSeconds: 0.5} so a finished sentence still triggers a fast turn — the delay only kicks in on genuine silence.
Why stopSpeakingPlan.numWords: 0?
numWords: 0 + voiceSeconds: 0.1 means any detected speech barges in immediately, no minimum-word buffer. Someone panicking about fraud doesn't finish a polite sentence before interrupting.
Why does the greeting land one turn late?
firstMessageMode: assistant-speaks-first fires before the tool-call pipeline attaches to the session — confirmed via a live GET /call/{id} log showing lookup_customer silently never firing on turn 0. Static firstMessage string now; the lookup runs on the model's first generated turn instead.
Why a code, not an identity check?
Coinbase's real verification is a government ID in-app — nothing phone-based to imitate. The OTP session is keyed by Vapi's call.id in DynamoDB with expires_at as a native TTL attribute; it confirms reachability only, for 5 minutes, then it's gone.
Why revert away from a "nicer" voice?
ElevenLabs (Lily → Brittney) measured ~9s of dead air per turn — likely TTS-queue latency on a shared, non-dedicated provider key. voice: {provider: "vapi", voiceId: "Emma"/"Sid", speed: 0.93} keeps first-audio-byte latency in-house.
Why drop phone_number from the create_ticket schema?
It was a field on the tool schema and the DynamoDB item, read by nothing downstream — send_verification_code/verify_code only ever touch email. Removed via PATCH /tool/{id}, the prompt, and the Lambda handler in one pass.
Architecture
Three plain, boring AWS pieces, plus Resend for email — chosen because each one earned its place, not because it's what everyone reaches for. The reasoning is below the diagram.
Why these specific AWS primitives?
One SAM template, four AWS::Serverless::* resources: an HttpApi event source (API Gateway v2), one AWS::Serverless::Function (Python 3.12, 128MB, 10s timeout), two AWS::DynamoDB::Table (PAY_PER_REQUEST), and the Resend API key as a NoEcho parameter — email goes out over Resend's HTTPS API. No custom runtime, no self-managed queue.
Why one Lambda, not four?
A single app.lambda_handler dispatches on event["rawPath"] — Vapi's toolCallList webhook shape on /tool-call, a plain JSON body on /register-customer — instead of four near-identical functions behind four routes needing the same fix each time.
Why DynamoDB only for OTP sessions and tickets?
jenny-otp-sessions keys on Vapi's call.id, with expires_at as a native TTL attribute — the one piece of state that has to survive two separate Lambda invocations in the same call (send → verify). Category walks and lookups stay stateless, single-invocation reads via DynamoDBCrudPolicy-scoped access.
Why Deepgram nova-3 with keyterm boosting?
A caller's email kept mistranscribing even after correction. Reading the raw STT output in CloudWatch — not just the model's replies — isolated the failure to the listening step. Swapped in nova-3 with a keyterm list (gmail.com, ACH, ACATS, Coinbase, …) to bias recognition toward domain vocabulary.
Why CORS-enable the HTTP API?
The /register-customer route needs a browser fetch() from a real page — Claude Artifacts block arbitrary cross-origin requests by CSP, so the page can't live there. SAM's Globals: HttpApi: CorsConfiguration handles the OPTIONS preflight and response headers with zero Lambda-side code.
"Vapi," "coinbase," and "Gmail" wordmarks in this deck are unofficial text treatments in each company's published brand colors — not their registered logos. The email mockup on the fraud demo slide is a recreated layout, not a screenshot. On the architecture slide, the Vapi, AWS and Resend logos are their owners' trademarks, shown only to identify the tools used.