Embedding AI Voice Agents in a VoIP Portal

🔭 Scout's Take

Third-party AI voice platforms connect via SIP trunking with number transfers between platforms — every hop degrades call quality, which kills speech-to-text accuracy. Customers also don't want another portal to manage. This case study covers building a multi-tenant AI voice platform that embeds directly into NetSapiens customer portals, with JWT auth exchange, tenant isolation via row-level security, and server-side API key management.

The Challenge

Telecom customers manage their phone systems through NetSapiens portals. Extensions, phone numbers, call routing — it's all there. When they wanted to add AI voice agents to handle calls, the obvious solution was to point them at a third-party AI voice platform. But those integrations connect via SIP trunking with 10-digit phone number transfers between platforms. Every extra hop degrades call quality, which degrades speech-to-text accuracy, which means the AI agent hears garbage and responds with garbage. The integration itself becomes the bottleneck.

Beyond the technical issues, customers don't want to context-switch between portals. They don't want separate logins for every vendor feature. They want to manage AI agents in the same place they manage everything else — the NetSapiens portal they're already logged into.

NetSapiens is a closed platform, but that doesn't mean you can't extend it. You can inject custom JavaScript and CSS via configuration URLs, embed interfaces as iframes, and exchange authentication tokens via PostMessage. It's not as simple as dropping in a third-party widget — you have to build the portal integration, the auth exchange, and the tenant isolation yourself. But the building blocks are there if you know where to look.

The Approach

The solution is a portal shim that injects into NetSapiens and embeds the AI platform as an iframe. When a customer logs into their NetSapiens portal, a custom JavaScript file loads, adds a navigation button, and creates an iframe pointed at the AI platform. The iframe runs a React SPA that handles all the AI agent management UI.

Authentication works via JWT exchange. NetSapiens includes a signed JWT in the portal's JavaScript context that contains the user's domain and role. The portal shim extracts that JWT and sends it to the AI platform via PostMessage. The platform validates the JWT signature, extracts the tenant domain claim, and issues its own session JWT. From that point forward, the iframe is authenticated and scoped to the correct tenant.

Tenant isolation uses PostgreSQL row-level security. Every database table includes a domain column. Every query runs within a tenant-scoped context that filters rows automatically. A user from companyA.com can only see agents, conversations, and recordings for companyA.com. The database enforces this at the row level, so even a SQL injection wouldn't leak cross-tenant data.

Portal Embedding Flow

NetSapiens Portal Portal Shim (JS) Embedded Iframe React SPA AI Agent UI AI Platform Auth Exchange Fastify API PostgreSQL + RLS Telnyx Voice Layer PostMessage (NetSapiens JWT) Session JWT API proxy (keys server-side) 1. User logs into NetSapiens 2. Portal shim injects iframe with JWT handshake 3. Platform validates JWT, issues session, scopes to tenant domain

Role-Based Access Control

The system supports three roles mapped from NetSapiens portal permissions:

The role comes from the scope claim in the NetSapiens JWT. The platform maps it to internal permissions and enforces them on every API request. An office manager from companyA.com can create agents for their domain but can't see or touch anything from companyB.com.

Architecture Diagram

Client Layer React SPA API Layer Fastify Tenant-scoped routes Integration Telnyx Proxy Keys never reach browser Data Layer PostgreSQL tenants (domain, status, plan) agents (domain, config) conversations (domain, call_id) Row-Level Security enforces tenant isolation Auth Flow 1. NetSapiens JWT → Platform validates signature 2. Extract domain claim → Issue session JWT 3. All API requests scoped to tenant domain

Technical Stack

The Outcome

Customers manage AI agents from the same portal they already use for everything else. No separate login, no vendor portal to learn. They click the AI Agents button in their NetSapiens navigation, the iframe loads, and they see their agents. Office managers create new agents, configure prompts, and test calls without leaving the portal.

Tenant isolation is enforced at the database layer. Every table with multi-tenant data includes a domain column and a row-level security policy. The application code sets the tenant context once per request, and PostgreSQL filters all queries automatically. A compromised API route can't leak cross-tenant data because the database won't return rows from other domains.

Telnyx handles the voice layer, but it's not a multi-tenant solution out of the box. One API key, one account — no concept of customer domains or tenant boundaries. We built the multi-tenant layer on our side: tenant-scoped API proxying, per-domain agent configuration, and server-side key management. The React app never sees the Telnyx API key, never stores it in localStorage, never includes it in network requests. That prevents key leakage via browser extensions, XSS attacks, or developer tools inspection.

The embedding strategy works within NetSapiens' constraints. You can't modify their portal directly, but you can inject JavaScript and CSS via configuration URLs. The portal shim is a single JavaScript file that adds a nav button and creates the iframe. The iframe communicates with the parent via PostMessage for authentication handshake and nothing else. The rest is a standard React SPA that runs independently inside the frame.