Telephony Quickstart
此内容尚不支持你的语言。
This is the full flow: your server asks Wetel to start a call, your page joins the call with the LiveKit client, and the agent answers. Read the overview for what a call is.
What you need
Section titled “What you need”- A named API key for your workspace (the same kind
sdkStartuses; see Authentication). - An agent in that workspace with a published workflow. Make it voice-safe before you test.
- Voice enabled on your environment, and a test session arranged with Wetel.
- A server that can make HTTPS requests, and a page served over HTTPS (browsers only give a page the microphone on a secure origin, or
localhost).
1. Start the call from your server
Section titled “1. Start the call from your server”Call the sdkStartVoiceCall mutation with your API key. Never call it from a browser: the key must not reach one.
// server.mjs — Node 20+const API_URL = process.env.WETEL_API_URL; // the GraphQL endpoint you were givenconst API_KEY = process.env.WETEL_API_KEY; // server-side only
const query = ` mutation StartVoiceCall($input: SdkStartVoiceCallInput!) { sdkStartVoiceCall(input: $input) { sessionId agentId livekitUrl livekitToken roomName expiresAt } }`;
export async function startVoiceCall(agentId, callerId) { const res = await fetch(API_URL, { method: "POST", headers: { "content-type": "application/json", "x-api-key": API_KEY, "x-huat-platform": "customer", }, body: JSON.stringify({ query, variables: { input: { agentId, clientId: callerId, // your own id for this caller or call context: { source: "web-call" }, // optional, flat object }, }, }), }); const json = await res.json(); if (json.errors) throw new Error(json.errors[0].message); const { livekitUrl, livekitToken, sessionId } = json.data.sdkStartVoiceCall; return { livekitUrl, livekitToken, sessionId };}Expose it to your page through your own endpoint, for example POST /my-app/start-call, which checks that the caller is allowed to call and returns only livekitUrl and livekitToken. The token is valid to join for about two minutes, so mint it when the caller presses the button, not earlier.
2. Join the call in the browser
Section titled “2. Join the call in the browser”This minimal page uses the LiveKit JavaScript client (livekit-client). It connects, turns the microphone on, plays the agent’s audio, shows the agent’s state and prints the live transcript.
<!doctype html><meta charset="utf-8" /><title>Voice call test</title><button id="call">Call the agent</button><button id="hangup" disabled>Hang up</button><p>Agent state: <b id="state">-</b></p><pre id="transcript"></pre><div id="audio"></div>
<script src="https://cdn.jsdelivr.net/npm/livekit-client@2/dist/livekit-client.umd.min.js"></script><script> const { Room, RoomEvent, Track } = LivekitClient; let room;
document.getElementById("call").onclick = async () => { // Your own endpoint from step 1. It returns { livekitUrl, livekitToken }. const r = await fetch("/my-app/start-call", { method: "POST" }); const { livekitUrl, livekitToken } = await r.json();
room = new Room();
// Play the agent's audio. room.on(RoomEvent.TrackSubscribed, track => { if (track.kind === Track.Kind.Audio) { document.getElementById("audio").appendChild(track.attach()); } });
// Agent state: listening, thinking or speaking. room.on(RoomEvent.ParticipantAttributesChanged, (changed, participant) => { if (changed["lk.agent.state"]) { document.getElementById("state").textContent = participant.attributes["lk.agent.state"]; } });
// Live transcript (the caller's speech and the agent's replies). room.registerTextStreamHandler("lk.transcription", async (reader, info) => { const text = await reader.readAll(); const final = reader.info.attributes?.["lk.transcription_final"] === "true"; if (final) { document.getElementById("transcript").textContent += `${info.identity}: ${text}\n`; } });
room.on(RoomEvent.Disconnected, () => { document.getElementById("hangup").disabled = true; });
await room.connect(livekitUrl, livekitToken); await room.localParticipant.setMicrophoneEnabled(true); document.getElementById("hangup").disabled = false; };
document.getElementById("hangup").onclick = () => room && room.disconnect();</script>Notes on the code:
- The browser asks for microphone permission at
setMicrophoneEnabled(true). Handle a refusal; the call is useless without it. lk.agent.stateis a LiveKit participant attribute that the agent participant sets tolistening,thinkingorspeaking. Use it for a “the agent is thinking” indicator.- Transcription arrives on the text stream topic
lk.transcription, and each stream carries the attributeslk.segment_id(which line it belongs to) andlk.transcription_final("true"once the line will not change). The example above prints only lines marked final. Treat the exact client API as that of thelivekit-clientversion you install, and pin it. - Pin the client script to an exact version in production rather than
@2.
3. What you should see
Section titled “3. What you should see”- The agent joins the room as soon as the caller joins and speaks first: a greeting that says it is an AI and offers a person.
- Talk. After you stop, the agent answers in about 3 to 5 s today.
- Talk over the agent. It stops speaking and answers what you said instead.
- Hang up. The session stays in the workspace like a chat session; read it with the sessions API.
If it does not work
Section titled “If it does not work”| Symptom | Likely cause |
|---|---|
Missing X-Api-Key header / Invalid API key | Header missing or key wrong or revoked (UNAUTHENTICATED). |
Voice calls are not enabled on this environment | Voice is not switched on for this environment (FORBIDDEN). Ask for a test session. |
Platform is not specified! | The x-huat-platform: customer header is missing. |
| Agent not found | agentId is not an agent of the key’s workspace (NOT_FOUND). |
| Joins, but nobody speaks | No voice worker is running for your session. Check the time window with your Wetel contact. |
| Join refused | The token is older than about two minutes. Start a new call. |
| “Too many requests” style error | Rate limit; wait a short while and retry. See the reference. |
When you report a problem, send the sessionId, the time with seconds and timezone, and what you saw. Never send the livekitToken or your API key.