Security
- 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
401at once. Use test keys (sk_test_…) everywhere except production.
Secret key scopes
Section titled “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, 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
Section titled “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
Section titled “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
Section titled “Your page’s Content-Security-Policy”If your site sends a Content-Security-Policy, allow the loader script and Nexafon’s frames:
Content-Security-Policy: script-src 'self' https://app.nexafone.so/embed; frame-src https://app.nexafone.soThe 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
Section titled “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
getUserTokencall. Answer401from your token endpoint when nobody is signed in,403to 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 sendCache-Control: no-store. - The SDK session lives only in the memory of Nexafon’s frames: nothing is written to your site’s
localStorage,sessionStorageor 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
Section titled “Webhooks”Verify every delivery’s signature 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.
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.