In-house R&D
Vocal — Real-Time Voice Platform
A one-to-one voice platform built end to end: WebRTC peer connections with automatic recovery when the network changes, a preference-based matchmaking queue, and a moderation console for reviewing and acting on reports.
The result
Audio travels peer-to-peer, so bandwidth cost stays flat as call minutes grow
Project overview
A one-to-one voice platform built end to end: WebRTC peer connections with automatic recovery when the network changes, a preference-based matchmaking queue, and a moderation console for reviewing and acting on reports.
Technology used
In one picture
The service introduces two people, then gets out of the way
Vocal connects two people for a voice conversation. The part that matters commercially is what happens after the introduction: the audio travels directly between the two devices and never passes through our servers.
Almost everything else about the system follows from that single decision. Bandwidth cost stops tracking how long people talk. The service cannot listen to conversations it is brokering, because the sound never arrives. And the server has no work to do during a call, so a thousand simultaneous conversations cost roughly what one does.
Architecture
Two planes, one boundary
The system separates the control plane — matchmaking, presence, moderation — from the media plane that carries voice. The control plane crosses the trust boundary into our infrastructure. The media plane never does; it begins and ends on the two devices.
State is classified per component rather than assumed. The matchmaking queue and room registry live in process memory, which is correct for a single instance and is also the constraint that would need Redis before a second instance could run. That limit is drawn on the diagram rather than discovered later by whoever inherits the system.
Reported IP addresses are hashed at ingress, so the database stores identifiers that support banning without retaining the addresses themselves.
Engineering detail
Connecting two browsers is an ordering problem
Establishing a peer connection looks like a tidy exchange of messages and is not. Network candidates are gathered the instant a local description resolves, concurrently with the offer and answer travelling between peers. Candidates therefore arrive routinely at a peer that has nothing to apply them to yet, and applying one early throws.
The failure this produces is intermittent and network-dependent: on a fast connection the answer usually wins the race and everything works, so it survives local testing and fails for real users. Candidates arriving early are buffered and drained at both points a remote description becomes available — draining at only one would fix one side and leave the other silently broken.
A second decision removes an entire class of failure rather than handling it. Because the server creates the pairing, it also assigns which peer makes the offer. Simultaneous negotiation — the condition the WebRTC specification devotes its perfect-negotiation pattern to resolving — cannot occur here.
Engineering detail
Calls that repair themselves
Networks change underneath live calls — a phone leaves wifi, an address is reassigned, a NAT rebinds. Most WebRTC applications never implement recovery at all: they connect, and when the path breaks the call simply ends.
Two degraded states are treated differently, because they mean different things. A dropped path frequently recovers on its own within a second or two, so it is given a three-second grace period and re-checked before anything happens — restarting immediately would discard a connection that was about to heal and replace it with a slower cold path. A terminal failure has nothing left to wait for and is acted on at once.
Only the peer designated as offerer may restart, reusing the role assigned at match time so recovery cannot reintroduce the simultaneous-negotiation problem. A cooldown released in a finally block means repeated failures degrade quietly instead of amplifying into a restart storm against an already-struggling network.
Engineering detail
Trading match quality against waiting
Strict preferences and short waits are in direct conflict whenever the queue is thin. Ask for a specific language and a specific partner, and if nobody matching is online the wait never ends. Ignore preferences and they become decoration.
The filter is treated as a function of waiting time rather than a fixed rule. For the first fifteen seconds preferences are enforced exactly as declared; past that threshold the effective preference used at comparison time relaxes, and the whole queue becomes matchable. The declared preferences are never rewritten, so there is no state to clean up and no way to leave someone permanently downgraded.
Compatibility decides who can match; a score decides who should. Shared language ranks best, a shared conversational mood earns a bonus, and ties fall to whoever waited longer. The queue is scanned oldest-first, so a better-scoring newcomer never overtakes someone already waiting.
Production readiness
Designed to scale, deliberately not scaled
The production topology was designed on AWS and held there. Actual demand was met comfortably by a simpler managed platform, and provisioning infrastructure for load that did not exist would have spent money to solve an imaginary problem.
What the design does contain is as informative as what it omits. There is no distributed cache at launch tier, because with a single task in-process state is the correct answer rather than a limitation. VPC endpoints replace a NAT gateway at roughly a fifth of the cost for this traffic. The database runs single-AZ with automated backups and point-in-time recovery — a stated trade of minutes of recovery time against half the cost.
Every resource is expressed in Terraform, with the right-sizing decision encoded as a variable rather than described in prose, so the architecture and the running infrastructure cannot drift apart. The scaling path is mapped through to internet scale, where the honest observation is that relay bandwidth, not compute, becomes the dominant cost.
The call we made
Built, then not launched
Vocal was finished and deployed before it was assessed properly against the category it sits in. That assessment concluded it should not launch publicly, and it did not.
Platforms pairing anonymous strangers for live conversation carry a specific and well-evidenced risk to minors. Meeting it requires proactive detection and real age verification, not the reactive report-and-ban tooling that had been built. Closing that gap honestly was a materially larger undertaking than the product itself, and shipping without it would have been the wrong decision regardless of how the engineering performed.
We would rather show work we stopped for a good reason than describe a launch that should not have happened. The same judgement is what a client is buying: an engineering partner who will say a thing should not ship, before the invoice rather than after it.
What we delivered
- ✓WebRTC signalling over Socket.IO, including ICE candidate buffering to handle candidates arriving before the remote description is set
- ✓Automatic connection recovery via ICE restart, distinguishing transient drops from terminal failures
- ✓Preference-based matchmaking that relaxes filters after 15 seconds to bound queue wait time
- ✓Moderation console with HMAC-signed admin sessions, report review, and ban enforcement at match time
- ✓Connection quality instrumentation feeding a live success-rate dashboard
- ✓Progressive web app with service worker and Web Push notifications
Want results like these?
Tell us about your project. We'll come back with an honest plan and a fixed quote.