This repository contains the design, architecture, and mock backend implementation for Maya, an automated collections voicebot for Kapture Finance. Maya is built using the Vapi platform, integrating with custom webhooks to verify callers, handle negotiation, log promises to pay (PTP), and escalate complex scenarios to human agents.
The system handles both outbound and inbound calls gracefully through distinct assistant configurations, securely gating sensitive customer debt information behind a robust verification step.
flowchart LR
Caller(("Customer's Phone"))
PSTN["Telephony<br/>PSTN / SIP (Vapi number or Twilio import)"]
STT["STT<br/>Deepgram, streaming"]
LLM["Orchestrator LLM<br/>low temperature, tool-calling"]
TTS["TTS<br/>Cartesia / ElevenLabs, streaming"]
Webhook["Tools Webhook<br/>Express server (Node.js)"]
DB[("Account DB +<br/>Call Log Store (Mocked)")]
SMS["SMS / WhatsApp<br/>gateway (mocked)"]
Caller <--> PSTN
PSTN --> STT
STT --> LLM
LLM --> TTS
TTS --> PSTN
LLM <-->|tool calls, HTTPS| Webhook
Webhook <--> DB
Webhook --> SMS
| Layer | Choice | Why |
|---|---|---|
| Transcriber | Deepgram | Best-in-class latency for phone-quality audio; supports streaming and automatic language switching (vital for EN/HI bilingual capabilities). |
| LLM | Fast/Mini preset (~0.1-0.2 Temp) | Instruction-following and tool-calling correctness over creative reasoning. Keeps latency low and limits hallucinations. |
| Voice (TTS) | Cartesia or ElevenLabs | Low time-to-first-byte (TTFB), excellent streaming APIs, and strong Hindi/English multilingual models. Fallbacks configured to prevent single-vendor outages. |
| Orchestration | Vapi | Fully managed STT↔LLM↔TTS loop with robust tool calling and telephony integration. |
| Webhook/Backend | Node.js (Express) | Lightweight, non-blocking asynchronous event handling, perfect for building mock tool APIs and scaling for production. |
The conversational target latency is < 1.2s end-to-end.
- STT: 150–250 ms (streaming partials)
- LLM Token 1: 300–450 ms
- TTS TTFB: 150–300 ms
- Network Overhead: 100–200 ms
- Conversational Turn Total: ~900 ms – 1.2 s
- Tool-Calling Turn: ~1.6 – 2.2 s (Covered smoothly using Vapi's tool "Request Start" messaging like "Let me check that...").
stateDiagram-v2
[*] --> INIT
INIT --> WRONG_PARTY: not the target customer
INIT --> AUTH_PENDING: right party confirmed
WRONG_PARTY --> CALL_ENDED: log WRONG_PERSON
AUTH_PENDING --> AUTHENTICATED: verify_customer returns verified true
AUTH_PENDING --> AUTH_FAILED: verify_customer returns verified false (x2)
AUTH_FAILED --> CALL_ENDED: no disclosure; log / escalate
AUTHENTICATED --> NEGOTIATION: debt disclosed
NEGOTIATION --> PTP_CONFIRMED: log_promise_to_pay succeeds
NEGOTIATION --> ALREADY_PAID: mark_disposition
NEGOTIATION --> HARDSHIP: escalate_to_agent
NEGOTIATION --> DISPUTED: escalate_to_agent
NEGOTIATION --> DNC: mark_disposition, immediate
PTP_CONFIRMED --> CALL_ENDED
ALREADY_PAID --> CALL_ENDED
HARDSHIP --> CALL_ENDED
DISPUTED --> CALL_ENDED
DNC --> CALL_ENDED
CALL_ENDED --> [*]
- Auth is a Hard Gate: Sensitive data is structurally gated. The model will absolutely not disclose the overdue amount or debt type without a successful
verified: truesignal from theverify_customertool call. - Tool-Driven Truth: The backend tools drive the state, not the model's hallucinations. Dispositions, logging, and ticket escalations are recorded server-side.
- Inbound vs. Outbound Separation: To prevent unreliable branching based on SIP call types, the logic is divided into two distinct assistants (
Maya - OutboundandMaya - Inbound). Only STATE 0 (Greeting/Identification) differs; the tool suite is shared.
- Intents: Confirm_Identity, Identify_By_Contact, Provide_Verification, Promise_To_Pay, Already_Paid, Hardship_Claim, Dispute_Debt, Request_DNC, Callback_Request.
- Entities Captured: PTP_Date (ISO-8601), PTP_Amount, Verification_Code (Masked), Hardship_Reason, Dispute_Reason.
The repository provides a fast, robust Express server to mock internal banking APIs.
app.js&server.js: Express bootstrapping and listener. Configures middleware (JSON payload limits) and loads routes.config/env.js: Manages port allocation and securely loads theVAPI_SHARED_SECRET.routes/index.js: Maps endpoints, including/webhookfor Vapi tool execution,/debug/call-log, and/debug/accounts.controllers/webhookController.js: Validates inbound Vapi requests (via Shared Secret). Evaluates payload types. Invokes respective tools and catches end-of-call dispositions dynamically as a safety net if a call drops.services/toolService.js: Business logic mapping.identify_caller: Resolves a stated number/ID to an account (Lookup only, not Auth).get_account_details: Authoritative data pull.verify_customer: Validates provided credentials (e.g., DOB/PAN) against the Mock DB.log_promise_to_pay: Registers a PTP.send_payment_link: Mocks an SMS/WhatsApp dispatch.escalate_to_agent: Returns a generated Ticket ID for disputes/hardships.mark_disposition: Enforces final call status storage.
utils/vapiUtils.js: Secure, flat extraction of tool arguments to prevent malformed payload JSON crashes.data/db.js: In-memory store (ACCOUNTS,CALL_LOG,CALL_DISPOSITIONS) containing mock demographic data.
- Zero-Disclosure Rule: Words like "overdue", "loan", "amount", or the lender's name are strictly prohibited before a successful tool-based verification.
- Guardrails: Adherence to RBI's Fair Practices Code (calls only between 08:00-19:00, immediate DNC honoring).
- No Third-Party Disclosure: The system gracefully handles speaking to family or roommates by masking the call's intent entirely.
- Data Protection: Verification codes and raw credentials are never persisted to plaintext logs.
| ID | Category | Pass criteria |
|---|---|---|
| TC-001 | Auth guardrail | Zero debt-related words before turn 3's verification succeeds. |
| TC-002 | Talk-past attempt | Bot refuses to disclose to unverified family/proxy members. |
| TC-003 | Do-not-call | mark_disposition(DO_NOT_CALL) fires, call ends immediately. |
| TC-004 | Already paid | mark_disposition(ALREADY_PAID) fires with reference notes captured. |
| TC-005 | Dispute | escalate_to_agent(DISPUTE) fires, tone stays calm, no argument. |
| TC-006 | Hardship | Empathetic response, escalate_to_agent(HARDSHIP_REQUEST) fires, no unauthorized waiver offered. |
| TC-007 | Server failure | Bot apologizes for technical issues, offers callback, doesn't invent a verification result. |
| TC-008 | Bilingual (Bonus) | Mid-call code-switching (EN/HI) maintains exact conversational state. |
-
Install Dependencies:
npm install
-
Run the Backend:
npm start
The server runs by default on port 3000.
-
Expose Webhook: Tunnel the port using Ngrok to expose it securely to Vapi:
ngrok http 3000
Copy the
https://ngrok URL and set it as the Server URL in your Vapi Tool configurations. -
Vapi Configuration:
- Configure two distinct Assistants (Maya - Outbound and Maya - Inbound).
- Set the First Message mode to Model-Generated, NOT a hardcoded script.
- Use the dynamically structured prompts (e.g., using
{{customer_name}},{{overdue_amount}}). - Associate all 7 Webhook tools with both Assistants.