# Security

> Keeping keys safe, scopes and IP allow-lists, allowed origins and embed domains, the Content-Security-Policy your pages need, and how sessions stay out of your page.

Source: https://docs.nexafone.so/v1/security/

## Keys

- **Secret keys** (`sk_…`, `psk_…`) stay on your servers: in environment variables or a secret store, never in a
  browser, an app, a repository or a log. Anyone holding one acts as your integration.
- **Publishable keys** (`pk_…`, `ppk_…`) may be public: they only start sessions, and only from their allowed origins
  (or app ids) together with a user token signed by your server.
- Keys are shown once. Nexafon stores only a hash, so a lost key is replaced, not recovered.
- **Rotate** by creating the new key, deploying it, then revoking the old one; a revoked key answers `401` at once.
  Use test keys (`sk_test_…`) everywhere except production.

### Secret key scopes

A secret key acts with its **scopes**: the permissions you give it when you create it, never more than your own. Give
each key only what its job needs. For keys that sign [user tokens](/v1/server/user-tokens/), the scopes are also the
most an embedded user may do, so include what your users need in the embed (for example `inbox:reply` and
`calls:answer`). The permissions:

| Permission | Lets the key | Needs plan feature |
| --- | --- | --- |
| `audit:view` | See the workspace audit log |  |
| `billing:manage` | Pay, top up, change seats |  |
| `billing:view` | See plan, wallet and invoices |  |
| `call_groups:manage` | Set how a team rings: strategy, order and timings |  |
| `call_reports:view` | See who answered and missed calls, per call and per staff member |  |
| `call_routing:manage` | Call menu drafts, schedules and holidays |  |
| `calls:answer` | Take WhatsApp and team calls |  |
| `calls:pickup_queue` | Pick any caller from a queue |  |
| `channels:manage` | Numbers, templates, IVR, hours |  |
| `commerce:manage` | Add, change and remove the store's products |  |
| `commerce:view` | See the store's products |  |
| `developers:manage` | API keys, webhooks, embeds |  |
| `files:view` | Open stored files; each kind of file also needs its own permission |  |
| `group_calls:start` | Call several staff members or a team at once |  |
| `inbox:assign_others` | Assign conversations to teammates |  |
| `inbox:reply` | Send messages and templates |  |
| `internal_calls:start` | Call one staff member inside Nexafon |  |
| `meetings:create` | Schedule voice meetings and run the ones you host |  |
| `meetings:manage` | Change, cancel and end any meeting and see its attendance |  |
| `numbers:manage` | Connect the business WhatsApp number |  |
| `onboarding:manage` | Run the setup wizard and see the setup checklist |  |
| `orders:manage` | Accept, update and cancel orders |  |
| `orders:view` | See the store's orders |  |
| `payments:manage` | Take and refund customer payments |  |
| `queue:pickup` | Pick up a waiting caller or take the next one from a queue |  |
| `recordings:download` | Download recordings |  |
| `recordings:listen` | Play call recordings |  |
| `recordings:manage` | Record, upload, replace and delete call audio |  |
| `recordings:view` | Hear the call audio library |  |
| `reports:detail` | See every report tab, agents and team calls, scoped to the teams you lead |  |
| `reports:schedule` | Schedule reports by email |  |
| `reports:view` | See team and call reports |  |
| `team:manage` | Invite, change roles, remove |  |
| `team:view` | See members, their roles and invitations |  |
| `templates:manage` | Create, edit, delete and sync WhatsApp templates |  |
| `templates:send` | Send approved WhatsApp templates |  |

### IP allow-lists

A secret key can be limited to your servers’ IP addresses or ranges; calls from anywhere else are refused.

## Origins and embed domains

Two lists protect the embed, and a page must be on both:

| List | Where | Protects |
| --- | --- | --- |
| A publishable key’s **allowed origins** | Developers → API keys | Which sites may start sessions with the key (`client_not_allowed`) |
| The workspace’s **embed domains** | Developers → Embed domains | Which sites may frame Nexafon’s screens: Nexafon sends them as the CSP `frame-ancestors` of its embed pages, so browsers refuse to show the frames elsewhere |

Enter origins as scheme and host (and port), such as `https://portal.example.com`, without a path. A workspace has at
most 20 embed domains. For partners, the partner’s **domains** serve as both lists for every store.

## Your page’s Content-Security-Policy

If your site sends a Content-Security-Policy, allow the loader script and Nexafon’s frames:

```http
Content-Security-Policy: script-src 'self' https://app.nexafone.so/embed; frame-src https://app.nexafone.so
```

The SDK needs nothing else from your page: the frames talk to Nexafon themselves, and `getUserToken` calls your own
server. With `popOutCalls`, pop-ups from your site must be allowed for calls to move to the pop-out window.

## Sessions stay out of your page

- The user token is short-lived (at most 10 minutes) and works once; fetch a new one for every `getUserToken` call.
  Answer `401` from your token endpoint when nobody is signed in, `403` to signed-in users who are not your staff
  (a token can make its user a member of your workspace, so never sign for customers who signed up), and send
  `Cache-Control: no-store`.
- The SDK session lives only in the memory of Nexafon’s frames: nothing is written to your site’s `localStorage`,
  `sessionStorage` or cookies, and your scripts never see the session token.
- Every message between your page and the frames checks the sender’s origin and the protocol version.
- Signing out of your product should end Nexafon too: call `Nexafon.destroy()`.

## Webhooks

Verify every delivery’s [signature](/v1/webhooks/signatures/) against the raw body, reject old timestamps, and dedupe
on the event `id`. Keep each endpoint’s secret like a secret key, and rotate it with the overlap window.

## Data

Every request runs inside the workspace it belongs to; ids from other workspaces answer `404`. Tenant secrets such as
WhatsApp and PayPoint keys are encrypted at rest and never returned by the API.
