TalkToMe
Real-time voice marketplace app for iOS and Android

Overview
TalkToMe is a real-time voice marketplace mobile app built for a client. Members browse available listeners, initiate paid voice calls, and rate their experience. Listeners set their per-minute rate, go live to accept calls, and earn money deposited directly to their bank account. The app runs on both iOS and Android from a single React Native codebase.
The client needed a turnkey voice marketplace where service providers — counselors, coaches, consultants — could monetize their time through on-demand voice calls. The platform handles everything end-to-end: real-time presence, WebRTC voice connections, per-minute billing with pre-authorization, and automatic payouts — so the client can focus on growing the marketplace rather than building infrastructure.
Built the entire system from scratch as the sole developer: native mobile app with Expo, serverless backend with Convex, WebRTC voice infrastructure with LiveKit, payment processing with Stripe, push notifications with Firebase, and EAS build pipelines for App Store and Play Store deployment.
Technical Highlights
Convex Serverless Backend: The entire backend runs on Convex — a real-time serverless platform that provides a reactive database, server-side functions (queries, mutations, actions), scheduled jobs, and HTTP endpoints for webhooks. The reactive data layer means listener availability, call status, and payment updates push to connected clients instantly without polling. Schema is defined in TypeScript with full type safety from database to UI.
LiveKit WebRTC Voice Calls: Real-time voice communication uses LiveKit's Selective Forwarding Unit (SFU) infrastructure. The backend generates short-lived access tokens with scoped room and participant permissions. Each call creates a unique LiveKit room, and both participants connect via WebRTC with automatic codec negotiation, echo cancellation, and network adaptation. The call screen handles connection states, audio routing, and duration tracking in real-time.
Stripe Payment Pipeline: A two-sided payment system using Stripe's Destination Charges pattern. When a member initiates a call, funds are pre-authorized (5-minute hold, fallback to 2-minute). After the call ends, only the actual duration is captured — 85% transfers automatically to the listener's Stripe Connect account, 15% is retained as platform fee. Zero-duration calls release the hold entirely. Card fingerprints are captured for ban enforcement across accounts.
Heartbeat Presence System: Listener availability uses a per-entity watchdog timer rather than global polling. Every 60 seconds, the app sends a heartbeat that reschedules a 70-second watchdog. If the watchdog fires without a fresh heartbeat, the listener is automatically marked offline. This gives a 10-second grace period for network hiccups while ensuring stale presence data is cleaned up without a centralized cron job.
Key Features
- Voice Calls: Real-time 1-on-1 voice calls via LiveKit WebRTC with automatic echo cancellation and network adaptation
- Pre-Authorized Billing: Per-minute billing with funds held before the call and actual duration captured after
- Stripe Connect Payouts: Listeners onboard via Express accounts and receive 85% of each call's revenue automatically
- Google OAuth: Authentication with secure session management and multi-device support
- Push Notifications: Firebase notifications for incoming calls on both iOS and Android, with automatic invalid token cleanup
- Presence System: Heartbeat-based listener availability with automatic offline detection via per-entity watchdog timers
- Listener Profiles: Customizable per-minute rates ($0.50–$100), bio, languages, location, and occupation
- Favorites & Blocking: Members save preferred listeners, listeners block unwanted callers
- Ratings: Post-call rating system (1–5 stars) with aggregate ratings displayed on listener profiles
- Anti-Abuse: Multi-factor ban enforcement using Google ID, email, device ID, and Stripe card fingerprint
Challenges & Solutions
Pre-Authorization Payment Flow: Members need funds reserved before a call starts, but the final charge depends on call duration. Stripe's manual capture PaymentIntents solve this — funds are authorized upfront for up to 5 minutes. If the member's card can't hold 5 minutes, the system falls back to 2 minutes and caps the call accordingly. After the call, only the actual amount is captured using Destination Charges that split revenue automatically. Edge cases like zero-duration calls and failed captures are handled gracefully.
Real-Time Presence Without Polling: Traditional presence systems use a cron job to sweep stale sessions, which doesn't scale well and introduces latency. Instead, each listener gets an individual watchdog timer via Convex's scheduler. The heartbeat-watchdog pattern ensures a listener is marked offline within 70 seconds of disconnecting, while avoiding the overhead of scanning all listeners on a fixed interval.
Cross-Platform Push Notifications: Incoming call notifications must arrive instantly on both iOS (APNs) and Android (FCM) — even when the app is killed. Firebase Cloud Messaging's HTTP v1 API provides a unified interface, but iOS requires additional APNs configuration with authentication keys. Built a notification system that handles platform-specific payload formatting (priority, TTL, sound, badge), multi-device delivery, and automatic cleanup of stale tokens when devices are unregistered.
WebRTC Connection Reliability: Voice calls need to work across varying network conditions — WiFi, cellular, network switches. LiveKit handles the low-level WebRTC complexity, but the app still needs to manage connection lifecycle: generating scoped tokens server-side, handling room join/leave events, managing audio routing (speaker vs earpiece), displaying connection quality indicators, and gracefully ending calls when either participant disconnects unexpectedly.
Multi-Factor Ban Enforcement: Banning a user by account alone doesn't prevent re-registration. The system captures multiple identifiers — hashed Google ID, normalized and hashed email, device ID, and Stripe card fingerprint — so a banned user can't simply create a new account. Ban checks run on login against all stored identifiers, and banned users see a dedicated screen with support contact options rather than a generic error.
Dual-Environment Build Pipeline: The app needs separate Firebase projects, Google OAuth credentials, and Stripe keys for development and production. Built an environment-aware configuration system using APP_VARIANT — EAS builds set the variant, which selects the correct Firebase config files (google-services.json, GoogleService-Info.plist) and environment variables. A custom Expo config plugin copies the right files into the native projects at build time.
Tech Stack
React Native
Cross-platform mobile framework
Expo 54
Development platform and build service
TypeScript
Type safety across app and backend
Convex
Real-time serverless backend and database
LiveKit
WebRTC voice call infrastructure
Stripe
Payment processing and Connect payouts
Firebase
Push notifications (FCM + APNs)
Google Sign-In
OAuth authentication
Jotai
Atomic state management
NativeWind
Tailwind CSS for React Native
Zod
Runtime schema validation
Expo Router
File-based navigation
EAS Build
App Store and Play Store CI/CD
Reanimated
Fluid animations
What I Learned
- Building a real-time marketplace from scratch for a client — handling the full product lifecycle from architecture decisions through App Store deployment
- Implementing a two-sided payment system with Stripe Destination Charges — pre-authorization holds, per-minute capture, automatic revenue splitting, and Connect payouts
- Real-time presence and voice call infrastructure using LiveKit WebRTC with server-side token generation, room management, and connection lifecycle handling
- Cross-platform mobile development with Expo and React Native — managing native modules, platform-specific push notification flows, and dual-environment builds for iOS and Android
- Working with a serverless reactive backend (Convex) where data changes push to clients instantly — a fundamentally different architecture from traditional REST APIs