v0.3 · private beta · 3 live providers

Intercom is text.
Bubblio is a person.

Drop a real-time AI customer-service character into any website. One signed callback. Your character reads and writes your data — and if a model provider goes down, we fail over to another automatically, so you never have to think about it.

$npm i @bubblio/widget @bubblio/serveror K in your editor
sess_b8e4f2a01c·region:iad1·rtt 38msLIVE
5lines
to embed
1API
every provider
0config
auto-failover
100%
callbacks signed

what you skip

You bring an idea. We bring the WebSocket, the lip-sync, the security, the metering.

Building this yourself means owning a streaming media pipeline and a tool RPC protocol. We did it once, so you don't have to.

without bubblio~1,400 lines · 3 services · pager rotation
×// Open Runway WS, manage reconnect, backoff…
const ws = new WebSocket(`wss://api.runway.com/v1/sessions/${id}`)
ws.addEventListener('message', (e) => {
  const { jsonrpc, method, params, id } = JSON.parse(e.data)
  if (method === 'tool_call') {
    const handler = TOOL_HANDLERS[params.name]
    const result = await handler(params.args)
    ws.send(JSON.stringify({ jsonrpc: '2.0', id, result }))
  }
})

×// Pipe browser audio to Runway, return generated audio
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
const ctx = new AudioContext({ sampleRate: 16000 })
const processor = ctx.createScriptProcessor(4096, 1, 1)
processor.onaudioprocess = (e) => {
  const pcm = float32ToInt16(e.inputBuffer.getChannelData(0))
  ws.send(pcm)
}

×// Decode video frames + sync to audio playback…
const decoder = new VideoDecoder({ output, error })
const playoutClock = new MediaTimingClock(audioCtx)
with bubblio~5 lines · 1 import · zero infra
+import { createBubblioSession } from '@bubblio/server'
+import { BubblioWidget } from '@bubblio/widget'

// 1. Server route — pass a callback URL.
const session = await createBubblioSession({
  bubblioApiKey: process.env.BUBBLIO_API_KEY,
  characterId: 'char_aria',
  tools: [{
    name: 'get_orders',
    callbackUrl: 'https://you.com/api/tools',
  }],
})

// 2. Frontend — drop in the widget.
<BubblioWidget config={{ characterId: 'char_aria', serverUrl: '/api/bubblio/session' }} />

architecture

Stripe webhooks, in reverse.

Bubblio holds the provider keys and picks a healthy one at connect time. When the character calls a tool, we POST a signed payload to your server. You return JSON. We log every call for billing and observability. You never touch a WebSocket — or a model outage.

browser
Visitor
5-line widget. Speaks, listens, sees the character.
  • <BubblioWidget />
  • mic + camera consent
  • WebRTC video stream
bubblio platform
Bubblio
We hold the provider keys, fail over if one is down, sign every callback.
  • provider failover
  • session metering
  • X-Bubblio-Signature
your server
Your code
Receive signed POSTs. Read your DB. Return JSON.
  • /api/bubblio/tools
  • verify HMAC
  • return Response.json(...)

integration

Server SDK in
your stack.

Bubblio manages the Runway WebSocket, RPC protocol, and session lifecycle. Define tools as callback URLs — we sign every request, you return JSON.

Next.jsExpressHonoFastifyRemixEdge runtimePythonGo
Tool callback payload
POST · /api/bubblio/tools
// POST https://you.com/api/bubblio/tools// X-Bubblio-Signature: sha256=8a1f...{"sessionId": "sess_b8e4f2a01c","customerId": "cust_acme_inc","tool": "get_orders","args": {"orderId": "ORD-4291"},"context": {"userId": "u_19a7","locale": "en-US"}}
route.ts
import { createBubblioSession } from '@bubblio/server'// app/api/bubblio/session/route.tsexport async function POST(req: Request) {const user = await getUser(req)const session = await createBubblioSession({bubblioApiKey: process.env.BUBBLIO_API_KEY,characterId: 'char_aria',personality: `Help \${user.name}.`,tools: [{  name: 'get_orders',  callbackUrl: 'https://you.com/api/tools',}]})return Response.json(session)}
i
One API, every provider. Bubblio runs your character on Runway, Tavus, or Anam — three live providers — and fails over automatically if one is down. Same five lines, no lock-in. Embedding on Webflow, Wix, or plain HTML? Read the embed guide →

resilience

One line of code. Every provider.
Your character never goes dark.

Realtime avatar providers have outages. Bubblio treats them as interchangeable: pick a preferred model or let us choose, and if it is unavailable when a visitor connects, we fail over to the next healthy one automatically — before the call even starts.

RunwayliveTavusliveAnamlive+ your modelsoon
Pick a model, or let us
Set a preferred provider, or leave it to Bubblio. Either way it is one field — not a second integration.
Instant failover at connect
Provider down when a visitor clicks? We route to the next healthy one before the session opens. Zero dev code.
Always the same 5 lines
Providers change behind the API. Your widget and your signed callback never do.

pricing

Pay per minute. No seats.

Free for approved teams during the private beta. Per-minute at general availability — one number you can predict, never a per-seat tax.

Growth
Production, at general availability
Usage/min
billed monthly
Per-minute · no seats
Enterprise
Custom SLA, white-label, volume
Custom
billed monthly
Dedicated capacity
What's a minute?
Live audio + video time per session. Idle widgets cost nothing.
Do tool calls cost extra?
No. Tool callbacks are unlimited on every plan.
What if a provider goes down?
Bubblio fails over to another healthy provider at connect time — zero config on your side.

Join the private beta.

Request access today — we approve new teams daily. Explore the dashboard and mint API keys while you wait; generate characters and go live the moment you're approved.

Request accessSee the code