slopus--happy
98e40dac97
CLI Smoke Test / smoke-test-linux (20) (push) Has been cancelled
CLI Smoke Test / smoke-test-linux (24) (push) Has been cancelled
CLI Smoke Test / smoke-test-windows (20) (push) Has been cancelled
CLI Smoke Test / smoke-test-windows (24) (push) Has been cancelled
Expo App TypeScript typecheck / typecheck (push) Has been cancelled
3.3 KiB
3.3 KiB
Paid Voice — Rate Limiting & Auth
Flow
User taps mic
│
├─ Bypass mode? (custom agent ID)
│ └─ connect directly to ElevenLabs, skip everything
│
├─ POST /v1/voice/conversations { agentId }
│ │
│ ├─ GET /v1/convai/conversations?agent_id=X&user_id=Y&created_after=<30d>&page_size=100
│ │ └─ Sum call_duration_secs → usedSeconds (~108ms)
│ │
│ ├─ conversations == 100? → { allowed: false, reason: "voice_conversation_limit_reached" }
│ ├─ usedSeconds >= 5h? → { allowed: false, reason: "voice_hard_limit_reached" }
│ ├─ usedSeconds >= 20min + no sub? → { allowed: false, reason: "subscription_required" }
│ │
│ ├─ GET /v1/convai/conversation/token?agent_id=X&participant_name=ELEVEN_USER_ID
│ │ └─ Decode JWT → extract conv_id from video.room
│ │
│ └─ Return { conversationToken, conversationId, agentId, elevenUserId, usedSeconds, limitSeconds }
│
├─ allowed: false?
│ ├─ "voice_conversation_limit_reached" → alert (file issue on GitHub)
│ └─ other → paywall flow="voice_must_pay"
│
└─ allowed: true
├─ feature flag voice-upsell == "show-paywall-before-first-voice-chat"?
│ └─ first free voice start only → soft paywall flow="voice_trial_eligible"
├─ feature flag voice-upsell == "voice-onboarding-and-upsell"?
│ └─ inject onboarding + upsell guidance into voice prompt
└─ otherwise
└─ control → no soft paywall and no onboarding experiment
then startSession({ conversationToken }) → WebRTC via LiveKit
Limits
| Tier | Limit | Window | Cost to us | What happens |
|---|---|---|---|---|
| Free | 20 min | 30 days | ~$0.19 | Paywall |
| Subscribed | 5 hours | 30 days | — | Hard block → BYO agent |
| BYO Agent | Unlimited | — | $0 | User's own ElevenLabs |
| Any | 100 conversations | 30 days | — | Hard block → file issue |
Cost: ~$0.01/min ($1600 / 171K min measured).
Tracking
ElevenLabs is the source of truth. No local DB.
participant_nameon token mint → setsuser_idon conversation record- Usage:
GET /conversations?user_id=Y&created_after=<30d>&page_size=100→ sum durations user_id= HMAC-SHA256 of Happy user ID (deterministic, one-way)- Max page_size is 100 → at 100 conversations we block (can't track more without pagination)
TODO: Remove VoiceConversation model from Prisma schema (no longer used, DB table can be dropped).
Paywall Flows (RevenueCat)
Single paywall template, rules driven by custom variable flow:
| Flow | When | Behavior |
|---|---|---|
voice_trial_eligible |
Feature flag variant show-paywall-before-first-voice-chat, first free voice use |
Soft — dismissable, voice starts anyway |
voice_must_pay |
Server returns allowed: false |
Hard — must purchase |
voluntary_support |
Settings | User-initiated |
Future: Voice Agent Self-Sell
Have the agent mention pricing naturally. Inject usedSeconds/limitSeconds into context, add showUpgradePaywall client tool.
Security
- JWT signed by ElevenLabs, single-use, can't be forged
- Agent set to "authorized only" — needs server-minted token
- Agent ID in public repo is harmless