> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wave.sa/llms.txt
> Use this file to discover all available pages before exploring further.

# Web Calling (WebRTC)

> Make voice calls straight from the browser with the Wave Calling SDK.

The **Calling SDK** lets your web app place real voice calls from the browser over
WebRTC — no plugins. You embed one script, hand it an API key, and call
`connect()` then `initCall()`. Wave handles the SIP registration, media, and the
short-lived session credentials for you.

<Note>
  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.
</Note>

## Install

<CodeGroup>
  ```bash npm theme={null}
  npm install @wave-sa/calling-sdk
  ```

  ```html CDN theme={null}
  <script src="https://unpkg.com/@wave-sa/calling-sdk"></script>
  <!-- exposes the global `Wave.SDK` -->
  ```
</CodeGroup>

## 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.

<Warning>
  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.
</Warning>

## Quickstart

```javascript theme={null}
import { WaveSDK } from "@wave-sa/calling-sdk";

const sdk = new WaveSDK(
  { apiKey: "sk_live_xxxxxxxxxxxx", apiUrl: "https://api.wave.sa/v1" },
  {
    onConnected: () => console.log("registered — ready to call"),
    onCallProgress: () => console.log("ringing…"),
    onCallConnected: () => console.log("in call"),
    onCallEnded: () => console.log("call ended"),
    onFailed: (err) => console.error(err.code, err.message),
  },
);

await sdk.connect();              // fetch session, open WebSocket, SIP REGISTER
await sdk.initCall("+966500000000"); // dial
// … later
await sdk.endCall();
sdk.disconnect();
```

## Configuration

`new WaveSDK(config, events)`

| Option        | Type             | Notes                                              |
| ------------- | ---------------- | -------------------------------------------------- |
| `apiKey`      | string           | **Required.** Your `sk_sandbox_` / `sk_live_` key. |
| `destination` | string           | Optional default number for `initCall()`.          |
| `locale`      | `"en"` \| `"ar"` | Prompt/UX locale. Default `"en"`.                  |
| `apiUrl`      | string           | API base. Default `https://api.wave.sa/v1`.        |

## Methods

| Method                            | What it does                                                                                |
| --------------------------------- | ------------------------------------------------------------------------------------------- |
| `connect()`                       | Fetches the session, opens the signaling socket, registers. Fires `onConnected` when ready. |
| `initCall(destination?)`          | Acquires the mic and dials. Returns `{ callSid, status: "initiated" }`.                     |
| `endCall()`                       | Hangs up the active call.                                                                   |
| `muteCall()` / `unmuteCall()`     | Mute or restore your microphone.                                                            |
| `holdCall()` / `resumeCall()`     | Place the call on hold or resume it.                                                        |
| `sendDTMF(tones, transportType?)` | Send keypad tones (`0–9 * # A–D`). Defaults to RFC 2833; pass `"INFO"` for SIP INFO.        |
| `disconnect()`                    | Tear everything down and release the mic.                                                   |
| `isMuted`                         | Current mute state (getter).                                                                |

## Events

Pass handlers in the second argument to the constructor.

| Event                        | Fires when                                                                                     |
| ---------------------------- | ---------------------------------------------------------------------------------------------- |
| `onConnecting()`             | The signaling channel is opening.                                                              |
| `onConnected()`              | Registered — ready to place a call.                                                            |
| `onCallProgress()`           | The far end is ringing (early media).                                                          |
| `onCallConnected()`          | The call was answered and media is flowing.                                                    |
| `onCallEnded()`              | You ended the call with `endCall()`.                                                           |
| `onCallDisconnected(reason)` | The call dropped — `reason` is `NETWORK_ERROR`, `TIMEOUT`, `SERVER_ERROR`, or `REMOTE_HANGUP`. |
| `onMuteStateChange(isMuted)` | After `muteCall()` / `unmuteCall()`.                                                           |
| `onHold()` / `onResumed()`   | The call was held / resumed.                                                                   |
| `onFailed(error)`            | Setup or registration failed — see error codes below.                                          |
| `onTokenWillExpire()`        | \~60s before the session credential expires (informational).                                   |
| `onSessionRefreshed()`       | The session credential rotated successfully.                                                   |
| `onSessionEnded(reason)`     | The session ended at Wave (`auth_failure` or `revoked`).                                       |

<Note>
  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.
</Note>

## Error codes

`onFailed(error)` gives you an `error.code`:

| Code                                     | Meaning                                                      |
| ---------------------------------------- | ------------------------------------------------------------ |
| `INVALID_API_KEY_FORMAT`                 | The `apiKey` isn't a recognized Wave key.                    |
| `UNSUPPORTED_BROWSER`                    | No WebRTC support.                                           |
| `MICROPHONE_UNAVAILABLE`                 | Mic permission denied or no input device.                    |
| `CONFIG_FAILED`                          | Couldn't fetch the session — often the origin isn't allowed. |
| `AUTH_FAILURE`                           | The key was rejected.                                        |
| `REGISTRATION_FAILED`                    | SIP registration failed.                                     |
| `TURN_UNAVAILABLE`                       | No media relay available on a restrictive network.           |
| `CALL_ALREADY_ACTIVE` / `NO_ACTIVE_CALL` | Call-state misuse.                                           |
| `NOT_CONNECTED`                          | `initCall()` before `connect()` finished.                    |
| `CALL_FAILED`                            | The call couldn't be set up.                                 |

## Next steps

<CardGroup cols={2}>
  <Card title="How voice works" icon="phone-volume" href="/voice/overview">
    Where a browser call sits in the call lifecycle.
  </Card>

  <Card title="Calls & recordings" icon="clock-rotate-left" href="/voice/calls-and-recordings">
    Read the log and fetch a recording.
  </Card>
</CardGroup>
