Skip to main content
This guide walks you from zero to a working “Talk to AI” button on your website using Truedy’s WebRTC API and the Ultravox browser SDK. By the end, a visitor clicks the button, the browser requests microphone access, and they are immediately in a live two-way voice conversation with your agent.

Prerequisites

  • A Truedy account with an active agent
  • Your Truedy API key (Settings → API Keys)
  • Node.js 18+ or Python 3.9+ for the server component
  • A modern browser (Chrome, Edge, Firefox, or Safari 15.4+)
Your Truedy API key must never appear in client-side code. All requests to api.truedy.ai must be made from your server. The browser only receives a short-lived joinUrl.

1

Install the Ultravox client SDK

The Ultravox SDK handles WebRTC negotiation and the audio session in the browser.
npm install ultravox-client
If you are not using a bundler, you can load it from a CDN:
<script type="module">
  import { UltravoxSession } from "https://esm.sh/ultravox-client";
</script>
2

Create a server endpoint that issues a joinUrl

Your server calls the Truedy API to start a WebRTC session and returns the single-use joinUrl to the browser. The browser never sees your API key.
import express from "express";

const app = express();

app.post("/api/start-call", async (req, res) => {
  try {
    const response = await fetch(
      "https://api.truedy.ai/api/public/v1/webrtc/call",
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.TRUEDY_API_KEY}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          agent_id: process.env.TRUEDY_AGENT_ID,
        }),
      }
    );

    if (!response.ok) {
      const error = await response.json();
      return res.status(502).json({ error: error.message ?? "Failed to start call" });
    }

    const { join_url } = await response.json();

    // join_url is single-use — send it directly, do not cache it
    res.json({ joinUrl: join_url });
  } catch (err) {
    console.error("start-call error:", err);
    res.status(500).json({ error: "Internal server error" });
  }
});

app.listen(3000, () => console.log("Server running on http://localhost:3000"));
Set the required environment variables before starting your server:
export TRUEDY_API_KEY="YOUR_API_KEY"
export TRUEDY_AGENT_ID="YOUR_AGENT_ID"
3

Build the browser page

Create a minimal index.html. This is a complete, self-contained example — no build step required.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Talk to AI</title>
  <style>
    body {
      font-family: system-ui, sans-serif;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      min-height: 100vh;
      margin: 0;
      background: #f9fafb;
      gap: 1rem;
    }
    #talk-btn {
      padding: 0.75rem 2rem;
      font-size: 1rem;
      border-radius: 9999px;
      border: none;
      background: #6366f1;
      color: white;
      cursor: pointer;
      transition: background 0.2s;
    }
    #talk-btn:hover  { background: #4f46e5; }
    #talk-btn:disabled { background: #a5b4fc; cursor: not-allowed; }
    #status { color: #6b7280; font-size: 0.9rem; }
  </style>
</head>
<body>
  <button id="talk-btn">Talk to AI</button>
  <p id="status">Click to start</p>

  <script type="module">
    import { UltravoxSession } from "https://esm.sh/ultravox-client";

    const btn = document.getElementById("talk-btn");
    const status = document.getElementById("status");
    let session = null;

    btn.addEventListener("click", async () => {
      if (session) {
        // End the active call
        await session.leaveCall();
        session = null;
        btn.textContent = "Talk to AI";
        status.textContent = "Call ended";
        return;
      }

      btn.disabled = true;
      status.textContent = "Connecting...";

      try {
        // 1. Fetch a single-use joinUrl from your server
        const res = await fetch("/api/start-call", { method: "POST" });
        if (!res.ok) throw new Error("Could not start call");
        const { joinUrl } = await res.json();

        // 2. Join the call — the SDK requests microphone access automatically
        session = new UltravoxSession();

        session.addEventListener("status", (e) => {
          status.textContent = e.state;
        });

        await session.joinCall(joinUrl);

        btn.disabled = false;
        btn.textContent = "End call";
      } catch (err) {
        console.error(err);
        status.textContent = "Connection failed — please try again";
        btn.disabled = false;
      }
    });
  </script>
</body>
</html>
4

Test it

  1. Start your server (node index.js or flask run)
  2. Open http://localhost:3000 in your browser
  3. Click Talk to AI
  4. Accept the microphone permission prompt
  5. Speak — your agent will respond in real time
Open your browser’s developer console to see SDK status events and any errors. The status event cycles through connecting → connected → active on a successful call.

Key concepts

The joinUrl is single-use

Every call to POST /webrtc/call produces a unique joinUrl that can only be joined once. Never cache or reuse it. If a user refreshes the page, your frontend must request a new joinUrl from your server.

API key stays on the server

Your Truedy API key authenticates you to api.truedy.ai. It must only ever exist in server-side code or environment variables — never in HTML, JavaScript files served to the browser, or version control.

Microphone permissions

The Ultravox SDK calls getUserMedia internally when joinCall is invoked. The browser will display a native permission prompt the first time. If the user denies microphone access, joinCall throws an error — handle it gracefully in your UI.

Next steps

Custom UI and live captions

Subscribe to transcript events for real-time captions and build a fully branded call UI.

Template variables

Pass dynamic data into your agent’s prompt at call time using template variables.

Widget builder

Use Truedy’s no-code widget builder to embed a pre-built voice button without writing frontend code.

Production security checklist

Review authentication, rate limiting, and webhook signature verification before going live.