Billing AI Phone Calls by the Minute (Without Losing Money)
On Envox AI, businesses create agents that answer and place real phone calls. Every second of those calls costs us money with the voice provider. If billing leaks, the business bleeds.
The entire money story converges on one place: the post-call webhook.
One webhook, one action
When a call ends, the voice provider sends us the transcript and metadata. A single domain action handles it:
- Work out the direction — inbound or outbound — from the caller metadata.
- Stamp a unique ID on every transcript turn (dedup insurance; webhooks retry).
- Compute
ceil(seconds / 60)and bill those minutes against the right quota — inbound and outbound are separate monthly features. - Apply discounts if the plan has them.
Everything billing-related is a named action: TrackFeatureUsageAction, CheckQuotaAvailabilityAction, UseCreditsAction, ResetUsageQuotasCommand on a cron. Twenty-five small classes instead of one God-service.
Why so much structure for billing?
Because billing bugs are the worst bugs. They're silent, they compound, and you discover them when a customer disputes an invoice or your provider bill doubles.
Structure buys you three things:
- Auditability. When someone asks "why was this customer charged 14 minutes?", the answer is one action with one log trail, not a grep across controllers.
- Safe evolution. Adding pay-as-you-go credits on top of quotas touched
UseCreditsActionand almost nothing else. - Testability. Proration on plan changes is pure logic. It should be tested like pure logic.
The rule I keep
Money math never lives in controllers. A webhook controller's only job is to authenticate the caller and hand off to the domain. The moment pricing logic sneaks into an HTTP layer, you've guaranteed the second copy of it will disagree with the first.
Boring? Completely. That's the point. Billing is one part of the stack where boring is a feature.
Enjoyed this post?
Subscribe to the newsletter
Get future posts delivered to your inbox. No spam, unsubscribe anytime.