BitVoice Pay
A voice-first Bitcoin Lightning wallet
MIT Bitcoin Hackathon 2026
Live demo: Operator dashboard · Design: Figma site · Github:Github repo
Inspiration
Over 1.4 billion adults worldwide lack access to a bank account (World Bank Global Findex–style estimates). Many still have access to a basic mobile phone not necessarily a smartphone, app store, or generous data plan while the Lightning Network can settle Bitcoin in milliseconds for negligible fees.
The bottleneck is rarely the protocol; it is the interface. Typical Lightning wallets assume a smartphone, installation, reliable internet, and comfort with seed phrases and QR codes. We asked: what is the smallest surface someone needs to send and receive Bitcoin?
For almost everyone, that surface is a phone call.
BitVoice Pay is built on that idea: Bitcoin over Lightning should feel like dialling a number, saying what you want, and hearing a confirmation. Callers do not install an app or use a mobile browser for the wallet itself. Today’s call flows and prompts are English-only; expanding languages is future work (see What’s next).
The voice session itself is a normal phone call: no BitVoice app and no website required to operate the wallet on the call. WhatsApp (invoices, receipts, invites) is separate: it needs mobile data or Wi‑Fi and the WhatsApp client, which is typically a smartphone- so the “inclusion” story is strongest for anyone who can place a call, with richer confirmation when they also have WhatsApp online.
What it does
BitVoice Pay is a voice-first Lightning wallet. Interaction is a normal phone call: speech plus keypad (DTMF) where needed.
Registration - Call the number, say your name (Twilio speech gather), choose a 6-digit PIN on the keypad. A per-user LNbits wallet is created and funded with starting satoshis. A WhatsApp welcome message confirms the wallet is ready when messaging is configured.
Balance - Say e.g. “balance” or “how much do I have?” and hear sats plus a USD equivalent (demo pricing helper).
Send - Natural phrases like “send five dollars to Alice” or “pay Bob a hundred sats.” The stack parses intent, reads back amount and recipient, asks for explicit voice confirmation, then executes a Lightning payment (same LNbits instance → instant, zero fee). WhatsApp receipts go to sender and recipient when delivery succeeds.
Receive - Say “receive”; a BOLT11 invoice is created and sent to WhatsApp (and summarized on the call).
History - Say “history”; the last five confirmed wallet payments from LNbits are read aloud.
Contacts - Say “add contact,” say a name, then enter the phone number on the keypad (with # to finish). If the contact is not on BitVoice yet, they can receive a WhatsApp invite.
Operator dashboard - A static HTML/JS page at
/dashboardshows users, aggregates, charts (e.g. 7-day view), and tables. A Server-Sent Events stream pushes a live call log; stats and tables are also refreshed on a timer (e.g. every 30 seconds) viafetch.
How we built it
Backend
Python 3.11, FastAPI, Uvicorn. Async SQLAlchemy with asyncpg for PostgreSQL in production (e.g. Railway). Local dev can use SQLite via DATABASE_URL in .env.example. Tables include users, contacts, and a TransactionLog audit trail.
Phone numbers are stored only as SHA-256 hashes (hex digest length (64)); PINs are bcrypt with cost factor (12) (work factor (2^{12}) in the usual bcrypt sense).
Voice layer (Twilio)
Webhooks return TwiML. Amazon Polly (Matthew) is used for <Say> consistency. DTMF gathers collect PINs and phone digits.
Main menu -
<Gather input="speech">→SpeechResult(Twilio STT) for short commands.RecordingUrl+ OpenAI Whisper remains a fallback path inprocess-commandif a recording is posted instead.Registration and add-contact names - Speech gather +
extract_name_from_text(GPT-4o-mini on the transcript), not a separate recording download on the happy path.Send field collection - Dedicated routes use speech gather for missing recipient or amount (low latency, no recording race).
Per-call state lives in an in-memory store keyed by Twilio CallSid (resets on process restart acceptable for a hackathon).
Lightning / money
Each user gets an LNbits wallet (User Manager on a hosted instance by default see LNBITS_URL in config). BitVoice stores per-user API keys.
Funding: the new wallet creates an invoice; the main wallet (LNBITS_ADMIN_KEY) pays it internal to the same node, instant and fee-free for the demo topology.
Internal BitVoice-to-BitVoice sends: recipient invoice → sender pays → same-node settlement; on-call copy highlights “fee was zero.”
AI / NLU
Twilio transcribes gathered speech (with
enhanced="true"where enabled in helpers)._keyword_intentfast-path handles short, common phrases (e.g. balance, goodbye, thanks) without calling GPT.Otherwise GPT-4o-mini returns structured JSON: intent (send, receive, history, add_contact, etc.) plus amount, currency, recipient when present.
parse_amount_from_speechhandles focused amount phrases; the intent layer maps noisy “sats” homophones (e.g. “set” / “sets”) toward satoshis in prompts.Send uses a field-collection pattern: whatever is missing after the first utterance is asked in follow-up speech steps before confirm-send.
Notifications
Twilio WhatsApp (Messages API): invoices, dual receipts after sends, welcome on registration, invites for new contacts. Voice flows do not block if WhatsApp fails.
Dashboard
REST JSON under /api/dashboard/* plus EventSource('/api/dashboard/stream') for the live log. Display times for dashboard analytics are aligned to US Eastern (America/New_York) in the API layer.
Challenges we ran into
Twilio WhatsApp sandbox - Recipients must opt in with a join keyword before outbound messages deliver. We track who has joined and keep voice flows working even when WhatsApp is skipped.
LNbits response shapes - Field types for amount, fee, and time shifted (e.g. numeric vs string, Unix vs ISO timestamps). Dashboard code uses defensive parsing (e.g. _parse_ts) and careful casting so mixed API versions do not silently show empty charts.
Voice UX - Every state must be speakable, errors recoverable in one step, and timeouts must not trap callers. Prompts were iterated for noisy lines and no screen.
NLU safety - Fixed intent set, read-back, regex-style confirmation (e.g. yes / yeah / confirm / ok only when not negated), and balance checks before paying.
Async under Twilio’s timeout budget — Handlers await DB, LNbits HTTP, and sometimes OpenAI. The dashboard uses asyncio.gather for parallel fetches and asyncio.wait_for around slow LNbits calls so one stall does not wedge the UI.
E.164 normalisation - Twilio sends From in E.164; keypad entry is raw digits. Helpers normalise 10-digit US, 11-digit leading 1, and longer international digit strings.
Accomplishments we are proud of
End-to-end voice Lightning payments on real phones, no consumer app.
Natural language that handles number words, currency noise, and sats mis-hearings well enough to demo reliably.
Zero-fee, instant same-node transfers with a memorable “fee was zero” moment.
Registration → funded wallet → WhatsApp welcome in under a minute when services are warm.
Live dashboard fed from real wallet APIs and a real-time call log stream.
Graceful degradation - failed WhatsApp, LNbits, or OpenAI paths still return valid TwiML and spoken errors where possible.
What we learned
The phone call is a harsh simplicity filter - If it cannot be said in one breath and understood without a screen, it probably should not ship.
LLMs are glue, not policy - GPT-4o-mini made intent parsing feasible quickly, but schemas, keyword shortcuts, and server-side validation against DB and balances are what make it safe.
Lightning over HTTPS “just works” for custodial demo wallets - The hard part was voice, timeouts, and integrations, not the core pay primitive.
Durable side channels matter - The call ends; WhatsApp, logs, and wallet history carry trust after hang-up.
Integrate against the deployed API, not the doc - One JSON shape change broke dashboards until parsing was hardened.
What’s next for BitVoice Pay
Multi-language voice - Today is English-only end-to-end; add detection and translated TwiML / STT paths.
External Lightning - Pay arbitrary BOLT11 / LNURL (read aloud or delivered by WhatsApp), not only co-resident wallets.
USSD / text fallback - For weak voice links, reuse backend logic over USSD or SMS/WhatsApp commands.
On-ramps - Fiat → Lightning via a partner to complete an inclusion story.
Production WhatsApp Business - Leave sandbox join friction behind.
Limits and controls - Daily send caps, optional second-factor confirmation for large amounts.
Non-custodial path - Today’s LNbits wallets are custodial; explore user-held keys (Phoenixd, self-hosted LNbits, etc.) for advanced users.
Math note (same-node fee)
For internal transfers on one LNbits node, a simple model is:
$$ \text{fee} \approx 0 \quad \text{(same logical node; demo / internal settlement).} $$
Routing on the public Lightning graph is out of scope for the current BitVoice-only send path but would satisfy (\text{fee} \geq 0) with typical millisat routing fees.
Built With
- asyncpg
- bcrypt
- fastapi
- httpx
- lnbits
- openai-gpt-4o-mini
- openai-whisper-api
- postgresql
- pydantic-settings
- python
- sqlalchemy
- twilio-programmable-voice
- twilio-whatsapp-api
- twiml
- uvicorn


Log in or sign up for Devpost to join the conversation.