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

# WebRTC security model

> Scoped single-use tokens, owned caller ID, account guardrails, and how to get a hard destination lock.

Browser calling uses a **scoped, single-use token** instead of a general-purpose calling credential.
This page is precise about what each path guarantees, so you can match it to your risk tolerance.

There are two tiers of enforcement:

* **Every path** issues scoped, short-lived, single-use tokens, validates the caller ID against
  numbers you own, attributes every call, and bounds the blast radius with account guardrails.
* **The server-originated paths** (join + bridge) additionally **lock the destination by
  construction**: the client never dials, so the call can't be redirected or replayed.

## How a token is issued and bounded (all paths)

```mermaid theme={null}
sequenceDiagram
  participant App as Your app
  participant API as RevDesk API
  participant SDK as @revdesk/webrtc

  App->>API: POST /v1/webrtc-token { from_number, to_number }
  API->>API: validate you OWN from_number (caller ID)
  API->>API: check usage limits + account guardrails
  API->>API: mint per-call credential (short TTL) + record the ticket (org, user, from→to)
  API-->>App: opaque token (rvw_…) + call_id
  App->>SDK: new RevDeskRTC({ token }), then connect() + dial()
  Note over App,SDK: token is single-use, valid for that one call only
```

The token you receive is an **opaque RevDesk token** (`rvw_…`), not a reusable calling credential. It
is issued for one call, tied to a caller ID you own, and recorded for attribution.

<Note>
  On the **browser-direct** path the destination is **declared, attributed, and guardrail-bounded**,
  but the browser is what dials, so the destination is not *locked by construction*. For a hard
  destination lock, use a server-originated path (below).
</Note>

## Hard destination lock: server-originated paths

On the **join** and **bridge** paths, RevDesk's server places the call and the client only joins it.
The destination is fixed server-side, so it **cannot be changed by the client and the token cannot be
replayed to reach a different number.**

```mermaid theme={null}
sequenceDiagram
  participant App as Your app
  participant RD as RevDesk (server)
  participant Dest as Called party

  App->>RD: request a call to to_number (from a number you own)
  RD->>Dest: server places the call (caller ID = your owned number)
  RD-->>App: client joins the call it set up
  Note over App,Dest: client never dials, destination is fixed server-side
```

### Bridge: caller-ID masking

When a person connects on their own phone (bridge), the called party only ever sees your RevDesk
number, never the person's personal number.

```mermaid theme={null}
sequenceDiagram
  participant App as Your app
  participant RD as RevDesk
  participant Agent as Your phone
  participant Dest as Called party

  App->>RD: POST /v1/calls/dial { from_number, to_number, connect_number }
  RD->>Agent: ring connect_number (caller ID = from_number)
  RD->>Dest: call to_number (caller ID = from_number)
  Note over Dest: sees from_number only, never connect_number
  Agent-->>RD: answers → bridged with called party
```

## Account guardrails (defense in depth, all paths)

These cap the blast radius even if a token were leaked:

```mermaid theme={null}
flowchart TB
  s1["Scope: a key limited to voice:webrtc"] --> s2["Ownership: caller ID must be a number you own"]
  s2 --> s3["Per-call token: single use, one call only"]
  s3 --> s4["Account profile: destination allowlist · daily spend cap · concurrency cap · max rate"]
```

## Security questions, answered

<AccordionGroup>
  <Accordion title="Can tokens be scoped (pinned from→to, single-use)?">
    Yes. Every token is issued for one `from_number` → `to_number`, is single-use, and is valid only
    for that one call. It's invalidated when the call ends (its lifetime covers the platform's max
    call duration so it never cuts an active call). On the browser-direct path the destination is
    declared, attributed, and bounded by the account guardrails. For a destination that **cannot** be
    changed or replayed, use a server-originated path (join or bridge), where the client never dials.
  </Accordion>

  <Accordion title="Can the token be tied to a single caller ID we choose?">
    Yes. `from_number` must be a number your organization owns; we validate ownership at issuance and
    only mint a token for a number you control.
  </Accordion>

  <Accordion title="What outbound guardrails apply?">
    A destination allowlist (blocks international/premium ranges), a daily spend limit, a concurrency
    cap, and a max per-minute rate, all enforced at the account level on every call, so a leaked token's
    blast radius is bounded.
  </Accordion>

  <Accordion title="How do calls map back to a specific user?">
    Every call is anchored to a record carrying your organization, the issuing user, and the from/to.
    Reconcile with `GET /v1/calls`.
  </Accordion>

  <Accordion title="Is there a proxy number I dial that forwards to the other party?">
    No, by design. A number anyone can dial can't authenticate the caller, lock the destination, or
    mask caller ID per call. The bridge path places both legs from the server instead: you call
    `POST /v1/calls/dial`, RevDesk rings your phone and the called party, then bridges them. That
    server-placed model is what makes per-call attribution and caller-ID masking possible. For a phone
    (cellular, no browser), bridge is the path; see [Choosing a calling path](/api-reference/calling-paths).
  </Accordion>

  <Accordion title="Can a token be used outside our app / replayed?">
    It's single-use (one call only) and guardrail-bounded. To make a token **non-replayable for
    origination**, usable only through a call RevDesk sets up, use a server-placed path (B or C):
    the client holds no dialing credential at all, only a grant to join a call RevDesk placed. See
    [Choosing a calling path](/api-reference/calling-paths).
  </Accordion>
</AccordionGroup>
