Skip to main content
The Calling SDK lets your web app make and receive real voice calls in the browser over WebRTC — no plugins. You embed one script, give it an API key, and call connect() then initCall(). Wave handles the SIP registration, the media, and the short-lived session credentials for you.
Web Calling uses the webrtc:write scope. In sandbox, calls are origin-locked and destination-locked to your signup number; a production key (sk_live_) removes the destination lock.

Install

Before you start

  1. Allow your origin. In the Wave dashboard, add the origin your app runs on (e.g. https://app.example.com) to your project’s allowed origins. The SDK fetches its session from /v1/webrtc/config, which rejects any origin that isn’t on the list.
  2. Use a webrtc:write key. The key is sent from the browser, so it’s origin-locked by design.
A key embedded in a browser is visible to anyone who loads your page. Wave origin-locks it (and destination-locks it in sandbox) to contain the blast radius — but for production, treat the embedded key as public and scope it to webrtc:write only.

Quickstart

Agent sessions and caller sessions

Each browser session is one of two kinds. The identity option selects the kind. Identity rules
  • 1–64 characters from A–Z a–z 0–9 . _ -.
  • Use the same identity everywhere: in the SDK, in POST /v1/queues/agents/login, and in a callflow <Dial>.
  • The prefixes p_ (portal members) and c_ (caller sessions) are reserved. An identity with these prefixes gets 400 INVALID_IDENTITY.
  • If your tenant already has a SIP user with that name that Wave did not create (for example a desk phone), Wave does not change it and returns 409 IDENTITY_CONFLICT. Use a different identity.
  • Wave creates the SIP user on first use and sets a new password for each session. You never manage SIP users.
  • One session for each identity. If the same identity opens a second session (for example a second tab), the newest session wins. The older session gets onSessionEnded("revoked") at its next credential refresh and stops ringing.
  • Agent sessions need a production key (sk_live_). A sandbox key ignores identity and opens a caller session.

Receive calls

Set identity and add an onIncomingCall handler. The handler gets a call object:
  • One call at a time. While a call is active or ringing, a new incoming call gets 486 Busy Here. It does not reach onIncomingCall.
  • If the caller hangs up before you accept or reject, onIncomingCallCancelled() fires. After that, accept() and reject() on that call object throw NO_ACTIVE_CALL.
  • If the microphone is denied, accept() throws MICROPHONE_UNAVAILABLE and the call continues to ring. You can then call reject().
  • If you set onIncomingCall without identity, the constructor throws INCOMING_REQUIRES_IDENTITY.

Call quality

While a call is connected and not on hold, the SDK sends onQualityReport(metrics) every 5 seconds. Each report covers the last 5 seconds: Reports stop when the call ends or is put on hold, and start again when you resume the call. As a guide: mos of 4.0 or more is good, 3.6–4.0 is fair, and less than 3.6 is poor. The quality values stay in the browser; Wave does not receive them.

Configuration

new WaveSDK(config, events)

Methods

Events

Pass handlers in the second argument to the constructor.
You never handle the SIP password or session token yourself. The SDK fetches them, keeps the call alive, and auto-rotates the credential about a minute before it expires.

Error codes

onFailed(error) gives you an error.code: The session request (POST /v1/webrtc/config) can also fail with these API errors. The SDK reports them as CONFIG_FAILED, with the HTTP status in the message:

Webhooks

When a browser call ends, Wave sends the usual call.ended webhook for it (see Webhooks).

Next steps

How voice works

Where a browser call sits in the call lifecycle.

Calls & recordings

Read the log and fetch a recording.