CLOUDCX
CloudCX CloudCX Cloud
Doc · CX-ADM-TRN
Official Handbook & Training Track

CloudCX
Administrator
& Training Manual

Multi-tenant Omnichannel CCaaS

The complete reference and training course for operating the CloudCX control plane — resellers, tenants, billing, automatic call distribution, the voice SBC, every digital channel, supervisor quality tooling, AI insights, security and day-two operations. Every screen is reproduced with annotated walkthroughs and hands-on exercises.

Multi-tenant CCaaS Voice · WhatsApp · SMS · Email · Web Chat White-label resellers AI insights Training exercises
Audience Platform Administrators & Trainees
CloudCX console: admin.cloudcx.app Edition 1.0 · v4.8 · 2026
© 2026 CloudCX. CloudCX is a CloudCX product.
CloudCXCloudCX Administrator & Training Manual
Contents
Navigation

Table of Contents

This manual is organised into the chapters listed below. Use the chapter and section numbers (for example § 7.4) as the stable cross-reference throughout — printed page numbers depend on your browser’s print engine and zoom.

01Welcome and how to use this manual02CloudCX platform overview & architecture03Signing in and a tour of the admin console04Roles and the access model05Resellers — create, brand, entitle, white-label06Tenants — create, configure, license07Users, teams, skills and per-user security08Numbers, SIP trunks and carriers, and LCR09The visual IVR / call-flow builder10ACD — queues, skills and routing strategies11Outbound — campaigns, dialer modes and DNC12The Voice channel — inbound, outbound, recording & screen-pop13Web chat and the embeddable widget14WhatsApp, SMS and email channels15Social channels — Facebook, Instagram, X16The unified agent inbox and cross-channel routing17Agent Desktop — Training Walkthrough18Supervisor wallboard and live monitoring19Quality — scorecards and evaluations20Surveys — CSAT and NPS21AI — insights, replies, bots and transcription22Contacts and CRM23Billing I — model, rate cards, invoicing and settlement24Billing II — Prepaid, Postpaid, Tax/FX, Dunning & Reports25Security and administration — credentials, IP rules, MFA/SSO, licensing, settings26Operations, health, troubleshooting and runbooks27Glossary and appendices
How to read this manual

Each chapter pairs a branded reproduction of the real CloudCX console screen — with numbered annotation markers — with step-by-step procedures, at least one worked example, and a Try it exercise for the training track. Watch for the four callout styles (Note, Tip, Warning, Danger): they carry the operational judgement around each task.

CloudCX · Administrator & Training ManualContents
CloudCXCloudCX Administrator & Training Manual
Ch. 01 · Welcome
Chapter 01

Welcome and how to use this manual

This is the official handbook and training course for CloudCX, the multi-tenant omnichannel contact-centre-as-a-service (CCaaS) platform. It teaches you to operate the platform control plane — the dark admin console at admin.cloudcx.app — from the very first sign-in through the day-two routines that keep resellers, tenants, channels and the voice fabric healthy. This first chapter explains who the book is for, the platform → reseller → tenant → user hierarchy that every later chapter assumes, and the conventions — callouts, screen mockups, numbered steps and Try-it exercises — the rest of the manual uses. Read it once; refer back to it whenever a later chapter’s notation is unclear.

Where you are

This manual documents the Platform Admin console — the topmost tier of the platform. Resellers and tenants have their own portals with the same look and a narrower scope; they are covered by the companion Reseller and Agent / Supervisor manuals. Wherever a screen shown here also appears (scoped down) in those portals, the chapter says so.

1.1 Who this manual is for

CloudCX is operated by a small number of trusted people with broad authority. The book is written for two overlapping readers, and most chapters serve both:

Platform administrators

The team that runs the whole CloudCX cloud: they create and cap resellers, provision tenants, manage wholesale rate cards and invoices, hold the shared provider keys, wire SIP carriers and least-cost routing, and own platform-wide security and licensing. In the user model this is the admin (often nicknamed super-admin) login, whose tenant_id and reseller_id are both empty — it belongs to no single customer and sees everything.

Trainees on the admin track

New operators learning the console before they touch production. Every procedural chapter pairs its instructions with at least one fully worked example and a Try it exercise you can rehearse in a sandbox tenant. Look for the dashed pink boxes; they are your hands-on checkpoints.

You do not need to be a developer to use this manual. You should, however, be comfortable with the vocabulary of a contact centre — queues, agents, DIDs, IVR, SLAs — and willing to work carefully, because actions taken in the admin console affect every customer on the platform. A short glossary of the terms used throughout appears in the appendix; the first time each term is used in a chapter it is briefly defined in place.

How to read it

You can read the manual front to back as a course, or jump to a chapter and use it as a reference. Cross-references use the stable section number (for example “see § 4.2”), never a page number — printed page numbers depend on your browser’s print engine and zoom. The Table of Contents at the front links to every chapter.

CloudCX · Administrator & Training ManualCh. 01 · Welcome
CloudCXCloudCX Administrator & Training Manual
Ch. 01 · Welcome

1.2 The platform at a glance: the tenancy hierarchy

Almost everything in CloudCX hangs off one idea: a strict, four-level tenancy hierarchy. Understanding it now will make every later chapter obvious, because each chapter is really about managing one level — or the relationship between two of them.

The platform → reseller → tenant → user hierarchy
Platform CloudCX cloud
Reseller white-label partner
Tenant customer / contact centre
Users TL & agents

Read it left to right as “owns”: the platform owns resellers; a reseller owns its tenants; a tenant owns its users. Money and entitlements flow the same way — the platform sets wholesale rates and seat caps for a reseller; the reseller resells retail plans and seats to its tenants. There is one deliberate shortcut: a tenant may be a direct platform customer with no reseller above it (its reseller_id is empty), in which case the platform plays the reseller’s role for that account.

1.2.1 What each level is

Platform Admin
You. The top of the tree — the CloudCX operator. Sees and governs every reseller, tenant, channel, carrier and key. Belongs to no customer (tenant_id and reseller_id are both null).
admin
Reseller
A white-label partner running CloudCX under its own brand — its own name, logo, colours and login domain. Has entitlement caps (max customers, agents, channels) and a wholesale plan, and may optionally bring its own SMS / WhatsApp / SIP provider credentials.
reseller
Tenant
A business actually running a contact centre on the platform. Owns its queues, DIDs, channels, contacts and people. Sits under a reseller, or directly under the platform.
tenant
Users
The people who log in for a tenant. A typed login: a tenant administrator, an optional sub-tenant (a department/business-unit split), a team leader (TL / supervisor) and the agents who handle interactions.
subtenant · tl · agent
The six login types

Every person who signs in anywhere on CloudCX has exactly one user type, drawn from a fixed set of six: admin, reseller, tenant, subtenant, tl (team leader) and agent. The type, together with the account’s owning tenant and reseller, decides what that login can see and do. This manual is concerned with the admin type; the others are operated from the reseller and agent portals.

CloudCX · Administrator & Training ManualCh. 01 · Welcome
CloudCXCloudCX Administrator & Training Manual
Ch. 01 · Welcome

1.2.2 Reference: the four levels side by side

The table below summarises the levels and where each is managed in the console. The Manage in column doubles as a road-map of the manual: each navigation group becomes one or more chapters.

LevelWhat it isOwnsManaged in (nav group)Login types
Platform The CloudCX cloud itself Resellers, shared keys, carriers, global policy Operate · Platform · Governance admin
Reseller White-label partner / brand Its tenants, brand, caps, wholesale plan Tenancy › Resellers reseller
Tenant A customer’s contact centre Queues, DIDs, channels, contacts, users Tenancy › Tenants tenant subtenant
Users People who handle interactions Their own sessions, presence & work Reseller / Agent portals tl agent
Table 1.1 — The tenancy hierarchy and where each level is administered.

1.2.3 Two conventions that follow from the hierarchy

Two platform-wide rules recur so often that they are worth fixing in your mind now. Both are called out again at the point of use, but you will save yourself surprises by learning them here.

0 means unlimited

Throughout the platform, an entitlement or cap of 0 means unlimited, not “none”. A reseller with max_customers = 0 may create any number of tenants. In the console this is shown as the infinity symbol (). Set a real positive number to impose a ceiling.

Bring-your-own credentials

By default every channel routes through CloudCX’s shared provider keys. A reseller may be permitted to bring its own SMS, WhatsApp or SIP credentials; when present, those take precedence for that reseller’s tenants. The toggles live on the reseller record (§ 4).

A third convention worth noting: every record has a status Active, Inactive or Locked. A login whose account (or whose tenant) is not active cannot sign in, regardless of password. You will use status constantly to suspend a partner or quarantine an account without deleting anything.

Why this matters for navigation

The console’s left-hand navigation is grouped to mirror this hierarchy: Tenancy for resellers and tenants, Revenue for the money that flows up the tree, Platform and Voice for the shared infrastructure every level uses, and Governance for the rules that bind them all. Chapter 2 tours that navigation in full.

CloudCX · Administrator & Training ManualCh. 01 · Welcome
CloudCXCloudCX Administrator & Training Manual
Ch. 01 · Welcome

1.3 Anatomy of a console screen

Every screen in the admin console shares the same frame: a dark navigation rail on the left, a light topbar across the top, and the working content beneath it. Because this manual reproduces each screen as a branded mockup, it helps to know the parts once. The figure below is the Dashboard — the first screen you see after signing in — with the persistent frame elements numbered.

admin.cloudcx.app
Operate
Dashboard01
Reports02
Tenancy
Resellers12
Tenants86
Revenue
Billing$
ALL SYSTEMS OPERATIONAL
v4.8.2 · eu-west · build 2026.06
Dashboard// platform overview · all resellers
ENV: PROD SA
// 01 — Platform overview

Platform overview

Live state of the entire CloudCX cloud — every reseller, tenant and channel in one place.

Refresh
Resellers
12
partners
Tenants
86
customers
Live calls
31
channels in use
Agents online
204
live presence

Live interactions · all tenants

31 active
Hourwise interactions chart · live call console · service health
1
2
3
4
5
6
7
8
  1. Brand & module tag — the CloudCX wordmark and the Admin badge confirm which console and tier you are in (reseller / agent portals show a different tag).
  2. Navigation rail — the dark left menu, grouped to mirror the hierarchy (Operate, Tenancy, Revenue, Platform, Voice, Governance). The active item is highlighted with a gradient bar. Counts (12, 86) update live.
  3. Breadcrumb — the current screen’s title and a mono subtitle describing its scope. This manual writes the same path as a TenancyResellers chip.
  4. Global search — jump to any reseller, tenant, DID or IP. Press ⌘K (macOS) or Ctrl+K from anywhere.
  5. Environment chip ENV: PROD tells you this is production, where every change is live. Always check it before acting.
  6. Account avatar — your initials, name and role (here SUPER-ADMIN). Opens the account / sign-out menu.
  7. Health & build footer — overall service health and the running version & region. Quote the build (v4.8.2) when raising a support ticket.
  8. Working area — the content for the selected screen: a page header (kicker, title, description, actions) above KPI tiles, panels and tables.
The Dashboard (Platform overview), with the persistent console frame numbered. Every later screen reuses this frame.
CloudCX · Administrator & Training ManualCh. 01 · Welcome
CloudCXCloudCX Administrator & Training Manual
Ch. 01 · Welcome

1.4 Your first sign-in

Before any of the rest of the manual is useful you must reach the console. Signing in is the simplest procedure in the book, so it is a good place to learn how procedures are presented here: a faithful screen, then numbered steps, then a worked example.

platform.admin.cloudcx.app
CloudCXCX Admin

Platform Admin

// sign in to the CloudCX control plane
Username or email[email protected]
Password••••••••••
Sign in
Forgot password?
PROTECTED · admin.cloudcx.app
1
2
3
4
  1. Username or email — your administrator login. Admin accounts are not tied to a tenant; you sign in with the credentials issued to you by CloudCX.
  2. Password — the focused field shows the pink focus ring used across the console for the field that currently has keyboard focus.
  3. Sign in — submits the credentials. If two-factor authentication is enabled on your account, a one-time code is requested next (see the note below).
  4. Protected banner — confirms the secure platform host. Bookmark platform.admin.cloudcx.app; never sign in from a link in an unsolicited email.
The Platform Admin sign-in gate. The console stays hidden until authentication succeeds.

1.4.1 Procedure — sign in to the console

  1. Open the console. Browse to platform.admin.cloudcx.app. If you have never signed in, the dark console is hidden and only the sign-in card is shown.
  2. Enter your username or email. Type the administrator identity issued to you.
  3. Enter your password, then select Sign in.
  4. Complete two-factor authentication if prompted. If your account has 2FA enabled, enter the 6-digit code from your authenticator app (or the one-time code emailed to you), then submit again.
  5. Confirm you have landed on the Dashboard and that the environment chip reads ENV: PROD. You are now in the control plane.
Two-factor authentication is opt-in

2FA (an authenticator app, or an email one-time code) is off by default and is enabled per account. If yours has it on, sign-in asks for the second factor only after your password is accepted; if it is off, you are signed in straight after the password. Enabling and managing 2FA is covered in the Security chapter (§ Governance).

CloudCX · Administrator & Training ManualCh. 01 · Welcome
CloudCXCloudCX Administrator & Training Manual
Ch. 01 · Welcome

1.4.2 Worked example — Aanya’s first morning

Worked examples run a procedure end-to-end with realistic values so you can see the result, not just the steps. Here is the sign-in procedure as a brand-new administrator would experience it.

Scenario

Aanya has just joined the CloudCX operations team. Her account [email protected] was created with the admin user type and 2FA switched on. It is her first morning and she wants to reach the Dashboard.

  1. She opens platform.admin.cloudcx.app. The dark console is hidden; only the Platform Admin sign-in card appears.
  2. She types [email protected] and her password, then selects Sign in.
  3. Because 2FA is enabled on her account, the card now asks for a 6-digit code. She opens her authenticator app, reads the current code and enters it.
  4. The console reveals itself on the Dashboard. The topbar shows her initials AA and the role SUPER-ADMIN; the chip reads ENV: PROD.
  5. She glances at the footer: ALL SYSTEMS OPERATIONAL · v4.8.2 · eu-west. Everything is healthy. Aanya is ready to work.

Result: a successful, MFA-protected sign-in landing on the platform overview. Had Aanya mistyped her password she would have seen “Incorrect username or password”; had her account been set to inactive she would have been refused with “User is not active” even with the right password.

1.5 The callout legend

Set off from the running text you will find four coloured callouts and one training box. Each carries a specific kind of operational judgement; learn to read them at a glance, because the most important safety guidance in this manual lives inside them.

Note

Background, context or a clarification. Useful to know, safe to skim. Notes never describe a risky action — they explain how something behaves or why a default is set the way it is.

Tip

A shortcut, best practice or recommendation that makes a task faster, cleaner or more reliable. Optional, but worth adopting — tips capture how experienced operators actually work.

Warning

Proceed with care. The action has consequences that are easy to get wrong — it may affect billing, interrupt live traffic, or be awkward to undo. Read the warning fully before you act.

Danger

Stop and think. This is a destructive or irreversible action — deleting a tenant, rotating a shared key, or anything that can cut off many customers at once. Be certain you are in the right place (check the environment chip) before continuing.

CloudCX · Administrator & Training ManualCh. 01 · Welcome
CloudCXCloudCX Administrator & Training Manual
Ch. 01 · Welcome

1.6 How screens, steps and exercises are presented

Every procedural chapter follows the same rhythm. Recognising it lets you skim for exactly the part you need.

1.6.1 Screens are reproduced, not screenshotted

Because the console evolves continuously, this manual does not paste live screenshots. Instead each screen is redrawn as a branded mockup that faithfully matches the real panel’s layout, fields and labels. A mockup carries small numbered annotation markers — the pink circles — and a matching numbered legend beneath it. Marker “3” in the picture always corresponds to item 3 in the list. Each figure ends with an auto-numbered caption such as Figure 1.4 (chapter 1, fourth figure), which is how figures are cross-referenced.

1.6.2 Procedures are numbered steps

Instructions are given as a numbered list with gradient step chips, like the sign-in procedure in § 1.4.1. Follow them in order. The lead phrase of each step (in bold) is the action; the rest of the sentence is the detail. A procedure is almost always followed by a worked example with concrete values.

1.6.3 Try-it exercises are for the training track

Trainees get hands-on practice through Try it boxes — the dashed pink panels. Each poses a small task to perform in a safe sandbox and lists what “done” looks like. They are optional for experienced administrators and essential for the training track.

1.6.4 Typographic conventions

A few notations recur throughout the manual:

UI label
Names of buttons, fields, menu items and screens are written in bold, exactly as they appear, e.g. select Add reseller.
Navigation path
A route through the menu is shown as a chip, e.g. TenancyResellersAdd reseller.
Address / URL
.path
Web addresses and console routes are pink mono, e.g. admin.cloudcx.app.
Code & field names
code
Literal values, field/data names and API fields are in mono, e.g. max_customers, reseller_id.
Keys
Key
Keyboard keys are shown raised, e.g. press ⌘K or Ctrl+K.
Section reference
Cross-references use the section number, e.g. “see § 4.2”. Tables are numbered per chapter (Table 1.1) as are figures (Figure 1.2).
Status pill
Record state is shown as a pill: Active, Inactive, Locked.
Print & share

This manual is built to print cleanly to A4 — use your browser’s Print (or “Save as PDF”) with background graphics enabled. On-screen running headers and footers are hidden in print; a watermark and page furniture take over.

CloudCX · Administrator & Training ManualCh. 01 · Welcome
CloudCXCloudCX Administrator & Training Manual
Ch. 01 · Welcome

1.7 What lies ahead

The chapters that follow walk the console in roughly the order you will use it, grouped exactly like the navigation rail. Use this as a map; each entry is a chapter you can jump to.

Operate

The Dashboard (platform overview & live state) and Reports / Analytics — reading the health of the whole cloud at a glance.

Tenancy

Resellers (brand, caps, bring-your-own credentials) and Tenants (the customers and their plans) — the heart of multi-tenant administration.

Revenue

Billing — accounts and balances, wholesale rate cards, the invoices CloudCX issues, and online (Stripe) or manual settlement.

Platform

Call Routing (SIP carriers & least-cost routing), Channels (the omnichannel toggles) and Platform Credentials (the shared, encrypted provider & AI keys).

Voice

IVR / Call Flows and outbound Campaigns — the visual builders for the voice fabric.

Governance

Security (identity, policy, 2FA, fraud defence), Licensing (the platform entitlement pool) and global Settings.

Practise in a sandbox first

You hold real authority over real customers. Until you are comfortable, run every Try-it exercise against a dedicated sandbox reseller and tenant — never against a live partner. Confirm the environment chip and the breadcrumb before any change, and prefer setting a record to inactive over deleting it.

Try it — orient yourself in the console

A five-minute warm-up to confirm you can read every part of the frame and the hierarchy. Do this in your training environment.

  1. Sign in following § 1.4.1 and confirm you land on the Dashboard.
  2. On the topbar, read aloud the four frame elements: breadcrumb title, environment chip, your role in the avatar, and the search box. Note whether the chip says PROD or a training value.
  3. In the navigation rail, name the six groups in order (Operate, Tenancy, Revenue, Platform, Voice, Governance). Note the live counts next to Resellers and Tenants.
  4. Open TenancyResellers and then TenancyTenants. For one tenant, identify which reseller (if any) owns it — that is the hierarchy from § 1.2 in the data.
  5. Read the footer and write down the build version and region.

Done when: you can point to all eight numbered frame elements from Figure 1.1 without looking them up, state which level of the hierarchy a given tenant sits at, and recite the build string from the footer.

Next chapter

With the hierarchy and conventions in hand, Chapter 2 gives you the guided tour of the console — the navigation rail, the topbar tools and the shared screen patterns — so that every screen thereafter feels familiar.

CloudCX · Administrator & Training ManualCh. 01 · Welcome
CloudCXCloudCX Administrator & Training Manual
Ch. 02 · Platform overview
Chapter 02

CloudCX platform overview & architecture

Before you provision a single reseller or route a single call, it pays to understand what CloudCX is and how its pieces fit together. This chapter is the mental model the rest of the manual builds on: the multi-tenant hierarchy you administer, the omnichannel queue every customer conversation flows through, and the planes — voice, application, data, channels and AI — that make it all run.

Read this as an administrator, not an engineer: you do not need to operate CloudCX SBC or CloudCX Switch by hand to run CloudCX well. But knowing which moving part does what will make every later chapter — trunks, ACD, channels, billing, security — click into place, and it will make you far faster when something needs diagnosing.

What you will learn

What CloudCX is as a product; the four-level tenancy model (platform → reseller → tenant → users); how a single queue serves six channels; the real architecture (telephony, application, data, channel and AI planes); and a worked example that traces one inbound call from carrier to conversation.

2.1 What CloudCX is

CloudCX is a multi-tenant, omnichannel Contact-Center-as-a-Service (CCaaS) platform. In plain terms: it is one cloud that lets many separate businesses each run a complete contact centre — phones, web chat, WhatsApp, SMS, email and social — without owning any telephony hardware, while you, the platform operator, run and monetise the whole thing from a single console at admin.cloudcx.app.

Two ideas distinguish CloudCX from a hosted PBX or a chat widget:

On top of that sits the commercial engine that makes CloudCX a platform rather than a single deployment: a white-label reseller tier, a per-feature, per-count licensing/entitlement engine, and built-in billing. Those three are what you spend most of your administrator time configuring, and they are the subject of Parts II and VIII.

Omnichannel

Voice plus five digital channels land in one queue and one agent desktop — with full customer context carried across them.

Multi-tenant

Strict per-tenant isolation. Every config row, CDR, recording and report is scoped to a tenant; the licence engine gates every count.

White-label

Resellers run the platform under their own brand, domain and pricing — optionally with their own SIP and channel credentials.

The one-line definition

Memorise this for tenant and reseller conversations: “CloudCX is one cloud that lets many businesses each run a complete, omnichannel contact centre — and lets partners resell that cloud under their own brand.”

CloudCX · Administrator & Training ManualChapter 02 · Platform overview & architecture
CloudCXCloudCX Administrator & Training Manual
Ch. 02 · Platform overview

2.2 The multi-tenant model

Everything you administer hangs off one four-level hierarchy. Each level owns the level below it and inherits limits from the level above. Getting this picture firmly in your head is the single most useful thing in this chapter — almost every screen in the console is a view onto one of these levels.

The CloudCX tenancy hierarchy — who owns whom
Platform
You. The operator of the entire CloudCX cloud. Owns shared infrastructure, the platform credential vault, global licensing and every reseller and direct tenant.
/admin
Reseller
A white-label partner that resells the platform under its own brand, domain and pricing. Has its own entitlement caps, billing plans and (optionally) its own SIP / channel credentials.
white-label
Tenant
A business/customer running one contact centre. Belongs to a reseller, or directly to the platform. Carries its own DIDs, queues, channels, recordings and CDRs — fully isolated from every other tenant.
customer
Users
The people inside a tenant: tenant admin, sub-tenant, team leader (TL), supervisor and agent. Each is a typed login with role-based permissions.
tenant admin · TL · supervisor · agent

In the data model these are exactly the records you will manage: a Reseller owns many Tenants (a tenant’s reseller_id can also be empty — a direct platform customer), and every User is typed as one of admin, reseller, tenant, subtenant, tl or agent. A platform admin’s tenant_id is null; a tenant user’s is set, which is what scopes them to their own data.

Table 2.1 — The four tenancy levels and what an administrator does at each
LevelLogin type(s)You administer hereConsole
PlatformadminResellers, direct tenants, platform credentials, global licensing, security, settings, routing & channelsAdmin (this manual)
ResellerresellerOwn brand & domain, own tenants, plans & rate cards, bring-your-own credentials, team & reportsReseller portal
Tenanttenant · subtenantExtensions, users, queues & skills, IVR/call-flows, campaigns, channels, surveys, tickets, reportsTenant admin
Userstl · supervisor · agent(They operate, not administer) live wallboard & QA · the omnichannel agent desktopSupervisor / Agent
Direct tenants vs. reseller tenants

Not every tenant needs a reseller. A tenant created with no owning reseller is a direct platform customer — you bill and support it yourself. A tenant created under a reseller inherits that reseller’s caps, branding and (where enabled) its credentials, and the reseller bills it. The same tenant provisioning form is used either way; the difference is whether you pick an owning reseller.

2.2.1 Isolation & entitlements — how separation is enforced

Two mechanisms keep tenants apart and keep the commercials honest:

“0 means unlimited” can bite you

Leaving an entitlement count at 0 grants unlimited, not zero. To actually disable a feature for a reseller or tenant, turn its toggle off — do not set the count to 0 expecting a hard stop. This convention runs through every limit field in the product.

CloudCX · Administrator & Training ManualChapter 02 · Platform overview & architecture
CloudCXCloudCX Administrator & Training Manual
Ch. 02 · Platform overview

2.2.2 Where each level lives in the console

The admin console’s left navigation is grouped to match the model above. Figure 2.1 maps every navigation group to the part of the platform it governs — the dashboard you land on after signing in.

admin.cloudcx.app
Operate
Dashboard01
Reports02
Tenancy
Resellers12
Tenants86
Revenue
Billing$
Platform
Call Routing
Channels6
Platform CredentialsKEYS
Governance
Security
Licensing
Settings
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Platform overview// all resellers · all tenants
ENV: PRODSA
Operate

Platform overview

Live state of the entire CloudCX cloud — every reseller, tenant and channel in one place.

Resellers
12
partners
Tenants
86
customers
Live calls
34
channels in use
Agents online
218
live presence

Hourwise interactions · today

voice + digital
1
2
3
4
5
  1. Operate — the live Dashboard and Reports: platform-wide KPIs, hourwise interactions and the live interaction console across every tenant.
  2. TenancyResellers and Tenants. This is the multi-tenant hierarchy itself; the pills show live counts (12 partners, 86 customers).
  3. GovernanceSecurity, Licensing and Settings: IP rules, password policy, MFA/SSO, the entitlement engine and platform-wide defaults.
  4. Search — jump to any reseller, tenant, DID or IP (⌘K).
  5. Environment & identity — the ENV: PROD chip and your platform-admin avatar; the green dot signals all systems operational.
The platform-admin dashboard, with the navigation groups mapped to the tenancy model.

Two more groups sit between Tenancy and Governance. RevenueBilling is where rate cards, invoicing and settlement live (Part VIII). Platform holds the shared plumbing every tenant draws on: Call Routing, Channels (the six-channel enable panel) and the Platform Credentials vault — covered next.

CloudCX · Administrator & Training ManualChapter 02 · Platform overview & architecture
CloudCXCloudCX Administrator & Training Manual
Ch. 02 · Platform overview

2.3 The omnichannel model — one queue, every channel

The word omnichannel is the heart of the product. Your customers reach out everywhere — they phone, they email, they message on WhatsApp, they text, they DM on social — and CloudCX answers them in one place. Every channel feeds the same routing brain, so an agent works a single, unified inbox with full context, rather than six disconnected tools.

CloudCX supports exactly six channels. These are the canonical set you enable per platform (and per tenant) on the Channels panel:

Table 2.2 — The six CloudCX channels and how each connects
ChannelKeyDirectionHow it connects
VoicevoiceIn & outSIP trunks → CloudCX SBC SBC → CloudCX Switch media
Web chatchatIn & outEmbeddable JS widget over WebSocket
WhatsAppwhatsappIn & outWhatsApp Business Cloud API (Meta) webhook
SMSsmsIn & outTwilio / Telnyx / CliSMS adapters
EmailemailIn & outIMAP ingest → threaded tickets → SMTP
SocialsocialIn & outFacebook / Instagram / X (Twitter) APIs

Whatever the channel, an inbound interaction becomes a record the routing brain understands: a voice call (a leg with a CDR) or an omni thread (a conversation on one channel with inbound/outbound messages). The ACD then distributes it to the best available agent — the very same queue-and-skill engine for chat as for calls.

Six channels in, one ACD, one agent desktop out
6 channelsvoice · chat · wa · sms · email · social
ACDqueues · skills · strategy
Agent desktopone unified inbox

2.3.1 The ACD — how an interaction finds an agent

The Automatic Contact Distributor (ACD) is the routing brain shared by voice and every digital channel. Four concepts drive it:

Queuequeues
A waiting line for one channel with a distribution strategy and an optional max_wait_seconds SLA. May be tenant-scoped or a shared platform default.
Skillskills
A competency tag, e.g. billing or spanish. Agents hold skills at a proficiency level; queues require them at a minimum level.
Queue memberqueue_members
An agent staffed on a queue — eligible to receive its interactions when present and within entitlement.
Strategyqueue_strategy
How the next agent is picked among the eligible, available members: round_robin, longest_idle, fewest_calls or priority.

Static configuration (membership + skills) lives in the database; live agent presence lives in CloudCX Cache. Routing joins the two in real time — which is why a queue can know, instantly, who is available and idle. ACD is covered in full in Chapter 10.

Why “one queue, every channel” matters commercially

Because the same ACD serves all six channels, a skilled agent can be staffed once and handle calls and chats and WhatsApp from a single presence. That is the efficiency story you sell to tenants — and the reason channel and agent-session entitlements are licensed separately in the engine.

CloudCX · Administrator & Training ManualChapter 02 · Platform overview & architecture
CloudCXCloudCX Administrator & Training Manual
Ch. 02 · Platform overview

2.4 The moving parts — CloudCX architecture

Under the console, CloudCX is a set of decoupled services grouped into five planes. You will rarely touch them directly, but every administrative action you take lands in one of them, and the health panel on your dashboard reports on them. Figure 2.2 is the map.

CloudCX planes — the architecture an administrator should recognise
Web plane
Role-scoped React console: admin, reseller, tenant, supervisor and agent. Talks REST + WebSocket. (This is what you are reading the manual about.)
React · WS
Application plane
The CloudCX Core app/API: authentication & RBAC, the licence engine, provisioning, the routing brain (ACD), the dialer, reporting/CDR, and the omnichannel router & webhook ingress.
CloudCX Core · /api/v1
Telephony plane
CloudCX SBC = SIP front door / SBC (registrar, TLS/SRTP, NAT, anti-fraud, dispatcher). CloudCX Switch = media/app server (IVR, queues, recording, conferencing). The app drives CloudCX Switch over control socket.
CloudCX SBC · CloudCX Switch · control socket
Channel plane
Independent workers feeding the same routing brain as voice: web-chat WS, email IMAP/SMTP, WhatsApp, SMS adapters and social.
5 digital channels
AI plane
Speech-to-text (default CloudCX Speech), text-to-speech, sentiment + summary over transcripts (CloudCX AI), and the chat/WhatsApp bot.
STT · TTS · CloudCX AI
Data plane
CloudCX DB (config, tenants/users, DIDs/trunks/rules, CDR, threads, reports — row-level scoped), CloudCX Cache (live presence, queues, pub/sub), and object storage for recordings/transcripts.
CloudCX DB · CloudCX Cache · object storage
The six planes. As an administrator you operate the web plane; everything below it is what your settings configure.

A few facts about this architecture are worth carrying with you, because they explain behaviour you will see in later chapters:

Staging is one host; production splits — same containers

In staging every plane runs on a single host; in production the telephony, media and app/data roles split across nodes. It is the same containers, only differently placed and env-configured. You administer CloudCX identically in both — the split is an operations concern, not a console one.

CloudCX · Administrator & Training ManualChapter 02 · Platform overview & architecture
CloudCXCloudCX Administrator & Training Manual
Ch. 02 · Platform overview

2.4.1 From carrier to conversation — one inbound call

To make the planes concrete, follow a single inbound phone call across them. This is the path every voice interaction takes, and it is the mental model to reach for when a call “doesn’t arrive” and you need to know where to look.

The life of one inbound call, plane by plane
CarrierSIP trunk
CloudCX SBCSBC · ACL · dispatch
CloudCX SwitchIVR · record
ACDqueue · skill
Agentdesktop
  1. A carrier delivers the call. A PSTN call reaches a DID you provisioned, arriving over a SIP trunk that terminates on the platform — no third-party CPaaS in the path.
  2. CloudCX SBC (the SBC) admits it. The signalling front door checks the source against the ACL / anti-fraud rules, handles TLS/SRTP and NAT, then the dispatcher load-balances the call onto a CloudCX Switch media node.
  3. CloudCX Switch answers and plays the flow. The media server runs the tenant’s IVR / call-flow, starts recording if enabled, and requests an agent. The application plane is watching via control socket.
  4. The ACD routes to an agent. The routing brain matches the interaction to a queue, filters by skill, applies the queue strategy against live CloudCX Cache presence, and rings the chosen agent.
  5. The agent handles it; data is captured. The agent talks on the unified desktop. On hangup a CDR is written, and — if configured — the recording is transcribed and scored for sentiment and a summary.

2.4.2 Platform vs. bring-your-own credentials

One architectural choice you will configure as a platform admin is where channel and AI credentials come from. CloudCX resolves them in a clear order, and the rule is the same for every channel:

Platform credentials

CloudCX’s own shared keys for SMS, WhatsApp, email and AI, held in the Platform Credentials vault. Used by every tenant whose reseller has not brought its own.

Bring-your-own (reseller)

When a reseller is allowed its own SMS, WhatsApp or SIP credentials, those take precedence for its tenants — its traffic runs on its accounts.

Every secret — a Twilio auth token, a Meta token, an IMAP password, the CloudCX AI key — is stored only as a Fernet-encrypted JSON blob; plaintext is never persisted, and no API ever returns a secret value. The single root key (BYOND_CREDS_KEY) lives in the server environment, outside the database.

No master key, no writes

If BYOND_CREDS_KEY is unset on the server, saving any credential returns 503 — the platform refuses to store a secret it cannot encrypt. Deleting a provider’s credentials is safe and idempotent, but the channel (or AI) goes idle until creds are configured again.

CloudCX · Administrator & Training ManualChapter 02 · Platform overview & architecture
CloudCXCloudCX Administrator & Training Manual
Ch. 02 · Platform overview

2.5 Worked example — tracing the hierarchy end to end

Let us make the model real with one concrete scenario you can hold in your head for the rest of the manual. A partner, Acme Communications, wants to resell CloudCX; their first customer is a retailer, Northwind Retail, who needs phones plus WhatsApp for a 25-seat support team.

Here is how that maps onto the four levels — and which later chapter does each step. You are reading the overview now; you will perform these in Parts II–IV.

  1. Platform → create the reseller. Under TenancyResellersAdd reseller you create Acme Communications: brand name, login_domain (e.g. cx.acme.com), caps such as max customers and max agents, and toggles for bring-your-own SMS / WhatsApp / SIP. (Chapter 5.)
  2. Reseller → create the tenant. Acme (or you, on their behalf) creates the tenant Northwind Retail, owned by Acme’s reseller_id, on a billing plan Acme offers. Northwind inherits Acme’s brand and credentials. (Chapter 6.)
  3. Tenant → set the entitlements. Northwind is licensed for 25 agent sessions, the voice and whatsapp channels (and their session counts), and the features they need — recording, QA, perhaps the AI bot. Counts left at 0 would be unlimited, so caps are set deliberately. (Chapters 6–7.)
  4. Tenant → provision telephony & the channel. A DID and the route to a SIP trunk are attached; WhatsApp is connected — on Acme’s own Meta credentials, because Acme’s bring-your-own-WhatsApp toggle is on. (Chapters 8 & 14.)
  5. Users → staff the queues. Northwind’s tenant admin creates a TL, a supervisor and 25 agents, tags them with skills (e.g. english, orders), and staffs them on a Support queue using longest_idle. (Chapters 7 & 10.)

The result is one branded slice of the platform: Acme sees only Acme; Northwind sees only Northwind; an inbound call to Northwind’s DID and an inbound WhatsApp message both land in the same Northwind queue and the same agent desktop — the omnichannel promise, delivered through the tenancy model.

Table 2.3 — The worked example mapped to levels, records and chapters
LevelIn this scenarioKey record / fieldWhere
PlatformYou operate the cloud/admin · platform credentialsThis manual
ResellerAcme CommunicationsReseller · login_domainCh. 5
TenantNorthwind RetailTenant.reseller_idCh. 6
Users1 TL · 1 supervisor · 25 agentsUser.user_type · skillsCh. 7
Channelsvoice + whatsapp into one queueQueue · OmniThreadCh. 10–14
Try it — place the pieces yourself

Goal: prove you can hold the whole model in your head before you touch a live console. On paper (or a whiteboard), do the following:

  1. Draw the four levels top to bottom and label each with its login type from Table 2.1.
  2. Add a second reseller, “Globex BPO”, and a direct tenant (no reseller) called “In-House Desk”. Show clearly which tenants Acme bills and which one you bill.
  3. For Northwind, list the six channels and tick the two that are enabled in the example. Beside the ticked ones, write whether each uses platform or bring-your-own credentials — and why.
  4. Trace a single inbound call across the five planes (carrier → agent) and name the component at each hop.

Check yourself: WhatsApp here runs on Acme’s own credentials (its bring-your-own toggle is on); voice rides the platform’s SIP unless Acme also brought its own trunk. If you set Northwind’s agent count to 0 you granted unlimited seats — not zero.

CloudCX · Administrator & Training ManualChapter 02 · Platform overview & architecture
CloudCXCloudCX Administrator & Training Manual
Ch. 02 · Platform overview

2.6 Chapter recap & key terms

If you take five things from this chapter, take these:

The product
Multi-tenant, omnichannel CCaaS — one cloud, many contact centres
The hierarchy
Platform → Reseller → Tenant → Users
The channels
voice · chat · whatsapp · sms · email · social (6)
The routing brain
One ACD — queues, skills, strategy — for every channel
Isolation & limits
Row-level tenant scope + entitlement engine (0 = unlimited)
Credentials
Platform vault by default; reseller bring-your-own takes precedence

2.6.1 Glossary of terms used in this chapter

CCaaSContact Center as a Service
A cloud contact centre delivered as a subscription — voice and digital channels, ACD, IVR, recording and reporting — with no on-premise hardware. CloudCX is a multi-tenant, resellable CCaaS.
Omnichannelone queue, every channel
Handling voice and all digital channels through a single routing brain and a single agent desktop, carrying customer context across them — as opposed to siloed, per-channel tools.
Tenantcustomer
A business running one contact centre on the platform, fully isolated by tenant_id. Owned by a reseller, or a direct platform customer when it has none.
Resellerwhite-label partner
A partner that resells CloudCX under its own brand, domain and pricing, with its own entitlement caps and (optionally) its own SIP / channel credentials.
ACDAutomatic Contact Distributor
The engine that distributes inbound interactions to the best available agent using queues, skills and a distribution strategy — shared by voice and every digital channel.
SBCSession Border Controller
The secure SIP front door — CloudCX SBC — doing registration, TLS/SRTP, NAT traversal, anti-fraud and load-balancing of calls onto the media servers.
control socketCloudCX Switch control socket Layer
The control channel between the application plane and CloudCX Switch — used to originate calls (dialer) and to drive supervisor monitor / whisper / barge.
Entitlementlicensed count / feature
A per-feature, per-count allowance enforced by middleware. By convention 0 means unlimited; disable a feature with its toggle, not by zeroing the count.
CDRCall Detail Record
The per-leg record written when a call ends — the basis for reporting and billing.
Where to go next

With the model in hand, Chapter 3 walks you through signing in — 2FA, email-OTP and password reset — and a guided tour of the console you saw in Figure 2.1. Chapter 4 then details the access model (platform admin · reseller · tenant · supervisor · agent) before Part II turns the worked example above into real resellers and tenants.

CloudCX · Administrator & Training ManualChapter 02 · Platform overview & architecture
CloudCXCloudCX Administrator & Training Manual
Chapter 03 · Signing in & the console
Chapter 03

Signing in and a tour of the admin console

This chapter takes you from a cold browser to a confident command of the CloudCX control plane. You will sign in for the first time, harden your own account with two-factor authentication (an authenticator app or emailed sign-in codes), learn how to recover a forgotten password, and then take a guided, fully annotated tour of every region of the console — the dark navigation rail, the light working area, and the controls you will reach for dozens of times a day.

Where this console lives

The Platform Admin Console is served at admin.cloudcx.app (the running header on the sign-in card reads admin.cloudcx.app). It is the top tier of CloudCX — the place you operate the whole multi-tenant cloud. Resellers use their own white-label console and tenants use theirs; both are covered in later chapters. Everything in this chapter applies to the platform admin sign-in.

3.1 Before you sign in

Account creation is not self-service at the platform tier. Your platform-administrator credentials are issued to you by CloudCX (or by an existing super-admin during onboarding, covered in Chapter 2). You will be given a username (or you may sign in with your email address) and an initial password. Have the following ready before you start:

Tip — bookmark the sign-in page

Bookmark admin.cloudcx.app directly rather than relying on a search engine. The first thing the console does on load is check for a valid session token; if none is present it shows the sign-in card automatically, so the same URL serves both as your login page and your home page.

3.2 The sign-in screen

When you open the console without an active session, the entire window is dimmed behind a centred sign-in card. Nothing else in the application is reachable until you authenticate — this is the login gate.

admin.cloudcx.app
CloudCXCX Admin
Welcome back
// sign in to the CloudCX control plane
Username or email[email protected]
Password••••••••••••
Sign in
PROTECTED · admin.cloudcx.app
1
2
3
4
  1. Brand & environment tag — the CloudCX wordmark with an Admin tag, confirming you are at the platform tier (not a reseller or tenant console).
  2. Username or email field — accepts either your assigned username or the email address on your account.
  3. Password field — your secret. Browsers may offer to save and autofill these credentials.
  4. Sign in button — submits the form. Its label changes to Signing in… while the request is in flight, and to Verify & sign in if a second factor is required (§ 3.4).
The sign-in gate over the dimmed console.

3.2.1 Signing in step by step

  1. Open the console. Navigate to admin.cloudcx.app. If you do not already have a live session, the sign-in card appears automatically.
  2. Enter your username or email. Type the identifier you were issued into the first field. It is not case-sensitive for email.
  3. Enter your password in the second field.
  4. Select Enter or click Sign in. The button shows Signing in… while it works.
  5. Land on the Dashboard. On success the gate disappears and the Platform overview (Dashboard) loads. Your session token is stored in the browser so you stay signed in across page reloads until you sign out or the token expires.
Warning — what the error messages mean

A red banner reading “Invalid username or password” means the credentials did not match. The platform deliberately does not say which of the two was wrong — this is by design, to avoid revealing whether an account exists. If you instead see “Could not reach the server”, the problem is your network or connectivity, not your credentials. A repeated, rapid burst of attempts is rate-limited: pause for a minute and try again.

3.3 Worked example — your first sign-in

Suppose you are Sofia Aranha, a newly onboarded platform super-admin. CloudCX has issued you the email [email protected] and a temporary password.

  1. Go to admin.cloudcx.app. The sign-in card appears.
  2. Type [email protected] into Username or email.
  3. Type the temporary password into Password.
  4. Click Sign in. The Dashboard loads, and the top-right corner now shows your identity chip — the avatar SA, the name S. Aranha, and the role SUPER-ADMIN — read live from your account.
  5. Immediately do two things: change your temporary password (use the Security view or your account profile), and enrol a second factor as described next in § 3.4. A platform super-admin account is the highest-value credential in the system; treat it accordingly.
Try it — sign in and read your identity chip

Sign in with your own credentials. Then look at the top-right of the topbar and write down the three things shown in your identity chip: your initials, your display name, and your role. Confirm the environment chip reads ENV: PROD. You will use these landmarks throughout the manual to confirm you are in the right place, as the right person, in the right environment.

CloudCX · Administrator & Training ManualChapter 03
CloudCXCloudCX Administrator & Training Manual
Chapter 03 · Signing in & the console

3.4 Two-factor authentication (2FA)

Two-factor authentication adds a second, time-limited proof of identity on top of your password, so that a stolen password alone cannot sign in. CloudCX gives you two independent options, and you choose one:

Authenticator app (TOTP)

A 6-digit code that rotates every 30 seconds inside an app on your phone — Google Authenticator, 1Password, Authy and the like. Works offline. This is the recommended option.

Email sign-in codes

A 6-digit code emailed to your verified address each time you sign in, valid for 10 minutes. Useful when you cannot run an authenticator app, but depends on email delivery being configured.

The two methods are mutually exclusive

You enable either the authenticator app or email codes — not both at once. If authenticator-app 2FA is on, the email-codes option is disabled until you turn the app off, and vice-versa. Both are opt-in and off by default: an account with neither enabled signs in with username and password exactly as in § 3.2.

Both controls live in the SecurityYour account area of the console: open Security from the Governance group in the sidebar, then scroll to the Two-factor authentication and Email sign-in codes panels. They act on your own signed-in account.

3.4.1 Enrolling an authenticator app (TOTP)

Enrolment is a deliberate two-step handshake: the console generates a secret and shows it to you, and you prove your app has stored it correctly by typing back one live code. Only then is 2FA switched on.

admin.cloudcx.app#security
Governance
Security
Licensing
Settings
ALL SYSTEMS OPERATIONAL
v4.8.2 · eu-west
Security// your account
PRODSA

Two-factor authentication

Setup in progress

Add this account to your authenticator app, then enter the 6-digit code it shows to finish.

Add this to your authenticator app:
Open in authenticator app
On a phone this opens your authenticator directly. On desktop, enter the secret manually →
Secret (manual entry)
JBSW Y3DP EHPK 3PXP K5GE
otpauth URI
otpauth://totp/CloudCX%20CaaS:[email protected]?secret=JBSWY3DP…&issuer=CloudCX%20CaaS
000000 Activate Cancel
1
2
3
4
5
  1. Security → your account — the active Governance item; the panels here act on your own login.
  2. Open in authenticator app — a tappable otpauth:// deep link; on a phone it adds the account to your app in one tap.
  3. Secret & otpauth URI — the same secret for manual entry on desktop, where deep links don’t apply. Type the spaced secret into your app.
  4. Code field — enter the live 6-digit code your app now shows for “CloudCX CaaS”.
  5. Activate / CancelActivate verifies the code and switches 2FA on; Cancel discards the pending secret.
The Two-factor panel mid-enrolment (setup in progress).
  1. Open Security. In the sidebar, under Governance, click Security. Scroll to Two-factor authentication. While 2FA is off it shows the badge Not enabled and an Enable 2FA button.
  2. Click Enable 2FA. The console generates a fresh secret and switches the panel into the Setup in progress state shown above. (Behind the scenes the secret is stored encrypted and pending; 2FA is not on yet.)
  3. Add the account to your app. On a phone, tap Open in authenticator app to import it in one step. On a desktop, open your authenticator, choose “add account / enter a setup key,” and type the Secret exactly as shown. The account will appear as CloudCX CaaS : your-email.
  4. Read the live code. Your app now shows a 6-digit code that changes every 30 seconds.
  5. Type the code and click Activate. If the code is correct, 2FA turns on and the panel reveals your one-time backup codes (see § 3.4.2). If it is rejected, your device clock may be out of sync — wait for the next code and try again.
Danger — do not lose the secret before you activate

The secret is shown only during this enrolment. If you navigate away or click Cancel before activating, the pending secret is discarded and you must start enrolment again from Enable 2FA. Never store the raw secret or the otpauth URI in a shared document — anyone holding it can generate your codes.

CloudCX · Administrator & Training ManualChapter 03
CloudCXCloudCX Administrator & Training Manual
Chapter 03 · Signing in & the console

3.4.2 Saving your backup codes

The moment 2FA activates, the console shows a set of one-time backup codes. Each works exactly once and lets you sign in if you ever lose access to your authenticator. This is the only time they are displayed — only their hashes are stored, so they can never be shown again.

admin.cloudcx.app#security

Two-factor authentication

Enabled
Enabled ✓Two-factor is now on.
Save your backup codes now
Each code works once if you lose access to your authenticator. Store them somewhere safe — they will not be shown again.
7H4K-9PXM Q2WT-MN83 VK59-CPJ7 D8XA-3RYH M6QB-W4ZP L3FN-72KD
I’ve saved my codes
1
2
3
  1. Enabled badge — confirmation that two-factor is now active on your account.
  2. Backup codes grid — ten single-use recovery codes, drawn from an unambiguous alphabet (no 0/O/1/I/L) to avoid mistyping.
  3. I’ve saved my codes — dismisses the panel only after you confirm. Click it once the codes are safely stored.
One-time backup codes, shown immediately after activation.
Warning — store backup codes offline

Save these codes in a password manager or print them and lock them away — not in the same place as your password, and never in plain text on a shared drive. After you click I’ve saved my codes, the Two-factor panel shows how many remain unused. If you run low, disable and re-enable 2FA to mint a fresh set.

3.4.3 Signing in with two-factor on

Once 2FA is active, your sign-in becomes a two-stage flow. You will not see the code field on the first screen — it appears only after your password is accepted.

  1. Enter username and password on the sign-in card and click Sign in as usual.
  2. A code field appears. Because your password was correct, the card now reveals a verification code field labelled “Enter the code from your authenticator app”, and the button changes to Verify & sign in.
  3. Type the current 6-digit code from your authenticator (or one unused backup code) and submit. A correct code completes the sign-in; a wrong one clears the field and shows “Invalid 2FA code” so you can retry with the next rotating code.
admin.cloudcx.app
Welcome back
// sign in to the CloudCX control plane
Username or email[email protected]
Password••••••••••
Enter the code from your authenticator app418 205
Verify & sign in
1
2
  1. Verification code field — appears only after the password is accepted; its label tells you whether to use your authenticator app or an emailed code.
  2. Verify & sign in — submits the second factor and completes the sign-in.
The second-factor challenge during sign-in.

3.4.4 Turning 2FA off

To remove authenticator-app 2FA, return to the Two-factor authentication panel (now showing Enabled ✓), enter a current 6-digit code (or an unused backup code) in the field, and click Disable 2FA. The secret and any remaining backup codes are cleared, and your account reverts to password-only sign-in until you enrol again.

CloudCX · Administrator & Training ManualChapter 03
CloudCXCloudCX Administrator & Training Manual
Chapter 03 · Signing in & the console

3.4.5 Email sign-in codes (the alternative second factor)

If you cannot run an authenticator app, CloudCX can email you a one-time code at each sign-in instead. This is controlled by the Email sign-in codes panel, just below the Two-factor panel in Security.

admin.cloudcx.app#security

Email sign-in codes

your account
OffEmail sign-in codes are off

When on, signing in asks for a 6-digit code emailed to [email protected]. Codes expire in 10 minutes. (Requires the Resend email credential to be configured.)

Turn on email codes
1
2
3
  1. State badgeOff or On. While authenticator-app 2FA is enabled, this panel tells you to turn that off first.
  2. Target address — codes go to the email on your account. Make sure it is correct and verified.
  3. Turn on email codes — enables the feature. After this, sign-in asks for an emailed code.
The Email sign-in codes panel (currently off).
  1. Confirm you have an email on file. If your account has no address, the panel asks you to add one first — emailed codes have nowhere to go without it.
  2. Click Turn on email codes. The badge flips to On.
  3. At your next sign-in, after your password is accepted, the card shows “We emailed you a 6-digit sign-in code — enter it below”. Check your inbox for a message titled “Your CloudCX sign-in code”, type the code, and click Verify & sign in.
Notes on emailed codes

Each code is valid for 10 minutes and a fresh request is throttled (you will not be re-sent a new code within about a minute). After several wrong attempts a code is burned and you must request a new one by signing in again. Email codes depend on the platform Resend credential being set (see the Platform Credentials chapter); if email delivery is not configured, prefer the authenticator-app method.

3.5 Forgot your password

If you cannot remember your password, use the self-service reset flow on the public site. It is intentionally privacy-preserving: requesting a reset always returns the same confirmation, whether or not the address is registered, so the page never reveals who has an account.

Request reset
/reset-password.html
Check email
“Reset your CloudCX password”
Open the link
valid 1 hour, single use
Set new password
policy-checked
The password-reset journey, request to new password.
  1. Open the reset page. Go to cloudcx.app/reset-password.html. With no token in the URL it shows “Reset your password” with a single Email address field.
  2. Enter your account email and click Send reset link. You will see “If that email is registered, a reset link is on its way.” — the same message regardless of whether the address exists.
  3. Open the email titled “Reset your CloudCX password” and click its button. The link is single-use and expires in 1 hour.
  4. Choose a new password. The link opens the page in its second mode, “Choose a new password”, with New password and Confirm new password fields. The new password must satisfy the platform’s password policy (minimum length and any required character classes — see the Security chapter).
  5. Submit and sign in. On success you see “Your password has been reset. You can now sign in.” Return to admin.cloudcx.app and sign in with the new password.
Warning — reset links expire and are single-use

A link that has been used, or is older than an hour, returns “This reset link is invalid or has expired.” Simply request a fresh one. A password reset does not remove your two-factor enrolment — if 2FA is on, you will still be challenged for a code after signing in with the new password. If you have lost both your password and your authenticator, sign in with the new password and one of your backup codes; if those are gone too, contact another super-admin to recover the account.

CloudCX · Administrator & Training ManualChapter 03
CloudCXCloudCX Administrator & Training Manual
Chapter 03 · Signing in & the console

3.6 A tour of the admin console

Now that you are signed in, take a moment to learn the lay of the land. Every screen in the console follows the same three-part frame: a fixed navigation rail on the left, a sticky topbar across the top, and the content area that fills the rest. Master these once and every later chapter will feel familiar.

admin.cloudcx.app
Operate
Dashboard// 01
Reports// 02
Tenancy
Resellers12
Tenants86
Revenue
Billing$
Platform
Call Routing
Channels6
Platform CredentialsKEYS
Governance
Security
Settings
ALL SYSTEMS OPERATIONAL
v4.8.2 · region eu-west
Dashboard// platform overview · all resellers
ENV: PROD SA
// 01 — Platform overview

Platform overview

Live state of the entire CloudCX cloud.

Resellers
12
partners
Tenants
86
live customers
Agents online
341
across all tenants
Live calls
57
in progress
1
2
3
4
5
6
7
  1. Brand block — the CloudCX wordmark and Admin tag at the top of the dark rail.
  2. Navigation groups — items organised under Operate, Tenancy, Revenue, Platform, Voice, Governance; the active item is highlighted with a gradient bar.
  3. Breadcrumb / page title — the current view’s name plus a mono sub-line describing scope.
  4. Global search — jump to resellers, tenants, DIDs or IPs; opens with ⌘K.
  5. Environment chip — a live green ENV: PROD badge so you always know which environment you are operating.
  6. Identity chip — your avatar, name and role; the sign-out control sits beside it.
  7. System health footer — overall status and the build/region line at the bottom of the rail.
The console shell — navigation rail, topbar and content.
CloudCX · Administrator & Training ManualChapter 03
CloudCXCloudCX Administrator & Training Manual
Chapter 03 · Signing in & the console

3.6.1 The navigation rail

The dark rail on the left is your map of the entire platform. Items are grouped by purpose, and clicking one swaps the content area to that view without a full page reload. Some items carry a small pill — a live count (for example the number of resellers or tenants) or a label such as NEW or KEYS. The currently selected item is marked with a gradient accent bar.

Table 3.1 — The admin navigation, group by group
GroupItemWhat it is forCovered in
OperateDashboardLive platform overview — resellers, tenants, channels and calls at a glance.Ch. 4
ReportsPlatform-wide analytics and exportable reporting.Ch. 4
TenancyResellersCreate and manage white-label partners and their entitlements.Ch. 5
TenantsManage the customer accounts that live under resellers.Ch. 6
RevenueBillingWholesale pricing, balances, statements and the payment provider.Ch. 7
PlatformCall RoutingSIP trunks and least-cost routing for voice.Ch. 8
ChannelsThe six omnichannel surfaces (voice, web chat, WhatsApp, SMS, email, social).Ch. 9
Platform CredentialsProvider API keys and secrets used across the platform.Ch. 10
VoiceIVR / Call FlowsThe visual IVR and call-flow builder (opens a dedicated page).Ch. 11
CampaignsOutbound campaign management (opens a dedicated page).Ch. 11
GovernanceSecurityPassword policy, your own 2FA, IP rules and SSO.Ch. 12
LicensingThe licensed seat and capacity pools allocated to resellers.Ch. 12
SettingsYour signed-in identity and platform configuration links.Ch. 12
Tip — pills are live numbers

The counts on Resellers and Tenants are read live from the API as the console loads, so they double as an at-a-glance scoreboard. If a pill shows a dash () the count could not be fetched — usually a transient connectivity issue rather than “zero.”

3.6.2 The topbar, search and your identity

The topbar stays pinned as you scroll. From left to right it shows the page title and scope, a global search box, the environment chip, a notifications bell with an unread badge, your identity chip, and the sign-out button.

Page title & scope
The name of the current view with a mono sub-line describing what you are looking at (for example // platform overview · all resellers).
Global search ⌘K
Search across resellers, tenants, DIDs and IP rules. The ⌘K (or Ctrl+K) shortcut focuses it from anywhere.
Environment chip
A pulsing ENV: PROD badge. Always glance here before a destructive action so you know you are in production, not a staging clone.
Notifications
The bell shows a count of unread platform alerts; click to review them.
Identity chip
Your initials avatar, display name and role (for example SUPER-ADMIN), read from your account via /users/me.
Sign out
Clears your session token from the browser and returns you to the sign-in gate. Always sign out on shared machines.

3.6.3 Signing out and sessions

  1. To sign out, click the sign-out icon at the far right of the topbar. Your token is removed and the sign-in card returns.
  2. Sessions persist across reloads. Until you sign out (or your token expires), refreshing the page keeps you signed in.
  3. Expired or revoked sessions self-heal. If the server ever rejects your token (for example after it expires), the console clears it and shows the sign-in gate automatically — just sign in again.
Try it — navigate the rail and use search

1) Click through every group in the navigation rail and note which views open a new page versus swapping the content area (hint: the Voice items open dedicated pages). 2) Press ⌘K (or Ctrl+K) to focus the global search and type the name of a reseller or a DID to see how the search scope behaves. 3) Finally, open Security and confirm your Two-factor authentication status — if it still reads Not enabled, complete § 3.4 now before moving on to Chapter 4.

You are ready for the rest of the manual

You can now sign in securely, protect your account with a second factor, recover a lost password, and find your way around every region of the console. The chapters that follow drill into each navigation group in turn — starting with the Dashboard and Reports in Chapter 4.

CloudCX · Administrator & Training ManualChapter 03
CloudCXCloudCX Administrator & Training Manual
Ch. 4 · Roles & Access
Chapter 04

Roles and the access model

Everything an account can see, change, or be billed for in CloudCX flows from a single decision: what type of login is this, and which slice of the platform does it own? This chapter is the canonical reference for the six login types, the three-tier tenancy hierarchy that contains them, and the row-level isolation model that keeps one customer’s data invisible to every other customer. Get this right and the rest of the manual — billing, channels, supervision, security — falls into place.

What you will learn

The difference between the six user types (admin, reseller, tenant, subtenant, tl, agent); the Platform Admin → Reseller → Tenant containment model; exactly what each role sees in its console; how the platform scopes every query so tenants never collide (the “tenant scope” rule); and how to provision, verify and harden each kind of account. Read this before any chapter that grants access to data.

4.1 The six login types at a glance

Every person (and every machine token) that signs in to CloudCX is a single User record carrying one user type. There are exactly six, and they never change at runtime — a login is created as a type and stays that type for its life. The type, together with two optional ownership pointers (tenant_id and reseller_id), is the only thing the platform consults when it decides what you may touch.

Table 4.1 — The six CloudCX user types and the scope each one owns
TypeWho it isSigns in atOwns / sees
admin Platform Administrator (CloudCX staff / you) admin.cloudcx.app The entire platform — every reseller, every tenant, every row.
reseller White-label partner operator cloudcx.app/reseller Only the tenants under their own reseller, and their own team.
tenant Customer account owner / administrator cloudcx.app/app Everything belonging to their one tenant.
subtenant A department / business-unit admin inside a tenant cloudcx.app/app Their tenant’s data (a delegated admin within the same tenant).
tl Team Leader / Supervisor cloudcx.app/supervisor Live ops & quality tools for their tenant; counts as a seat.
agent Front-line contact-centre agent cloudcx.app/agent Only their own queued work and presence; counts as a seat.
The mental model

Three of the types are tenancy tiers (admin → reseller → tenant) and three are roles inside a tenant (subtenant, tl, agent). tl is the platform’s internal name for a Team Leader; in every UI and throughout this manual it is presented as the Supervisor. The two seat-consuming types — agent and tl — are the ones that count against licensing entitlements (see Chapter 6).

4.2 The tenancy hierarchy

CloudCX is multi-tenant by construction. The platform is divided into three nested tiers of ownership. Each tier can only ever reach down into the tier it contains — never sideways to a sibling, and never up to its parent.

The three-tier containment model — each tier owns everything beneath it
Platform Admin
The control plane. Owns and operates the whole platform: creates resellers, creates direct tenants, manages shared credentials, sees every row in the database.
user_type = admin · tenant_id = NULL
Reseller
A white-label partner. Owns a set of tenants it has signed up, plus its own staff team. Brands the product, sets wholesale prices, and may bring its own SMS / WhatsApp / SIP credentials.
user_type = reseller · reseller_id set
Tenant
One customer business running its contact centre. Contains all of its own users (subtenant / tl / agent), contacts, conversations, calls, queues and billing. A tenant may be owned by a reseller, or be a direct platform customer.
user_type = tenant · tenant_id = self
Tenant users
The people who staff that tenant: subtenant (delegated admin), tl / Supervisor (live ops & QA), and agent (front-line). All carry the same tenant_id as their tenant.
subtenant · tl · agent — tenant_id = parent tenant

Two ownership pointers wire this together on every User row:

tenant_id
The tenant a login belongs to. NULL only for the Platform Admin.
reseller_id
The reseller a reseller-login belongs to. Set only on reseller users.

A Tenant itself also carries a reseller_id: when set, the tenant belongs to that reseller (white-label); when NULL, it is a direct platform customer that you, the admin, manage yourself. This single column is what lets a reseller see “their” tenants and nobody else’s.

Roles are not editable in place

A user’s type and its tenant are set at creation and are intentionally immutable through the console. To “promote” an agent to Supervisor you create a new tl login (or have an admin re-provision the account) — you cannot flip the type on an existing record. This keeps the isolation model auditable: a login’s blast radius never silently grows.

CloudCX · Administrator & Training ManualChapter 4 · Roles & the access model
CloudCXCloudCX Administrator & Training Manual
Ch. 4 · Roles & Access

4.3 The isolation model — how tenant scope works

The promise that makes a CCaaS platform safe to sell is simple to state: a user from one tenant can never read or change another tenant’s data. CloudCX enforces this not with hope, but with a single rule applied to every query that touches tenant-owned data. The platform calls it the tenant scope, and it resolves identically everywhere — the dashboard counts, the contact list, the live-call wallboard, every report.

When any login asks for tenant-owned rows, the platform narrows the query by the caller’s identity, in this exact order:

Table 4.2 — How the tenant scope resolves visibility, by login type
CallerRecognised when…Rows they can see
Platform Admin type = admin and tenant_id = NULL All rows — every tenant, plus platform-level rows that belong to no tenant.
Reseller user reseller_id is set Only rows whose tenant is one of this reseller’s tenants.
Tenant / subtenant / tl / agent tenant_id is set Only rows whose tenant_id equals their own.
Anything else no admin scope, no reseller, no tenant Nothing. The query returns an empty set.
Why a denied lookup says “Not found”, never “Forbidden”

When a login fetches a single record by id that it is not permitted to see, CloudCX returns 404 Not Foundnot 403 Forbidden. This is deliberate. A 403 would confirm the record exists; a 404 reveals nothing. So if a tenant user pastes another tenant’s contact ID into a URL, the platform behaves exactly as if that row did not exist. Do not file these 404s as bugs — they are the isolation model working.

4.3.1 Platform-level (tenant-NULL) rows

A few rows belong to no tenant: anonymous web-chat threads that arrive before they are claimed, platform demo data, and shared platform defaults. These tenant-NULL rows are visible to the Platform Admin only. No reseller and no tenant user can ever see them — the scope rule treats “belongs to no tenant” as “belongs to the platform.”

4.3.2 What the login token carries

After a successful sign-in (Chapter 3), CloudCX issues an access token that encodes the three facts the scope needs, so every later request is evaluated without a second password prompt:

Claims embedded in the access token
// decoded access-token payload (illustrative)
{
  "sub": "a3f1…",        // the user id
  "ut":  "tenant",      // user_type — drives every permission check
  "tid": "7c2e…",       // tenant_id  (null for the platform admin)
  "rid": null           // reseller_id (set only for reseller logins)
}
One rule, applied everywhere

You will see the same three-way fan-out (admin → all, reseller → own tenants, tenant → self) in the live-call wallboard, the open-chat counter, the CDR-today tile and every list in the product. There is no per-screen access logic to learn: master Table 4.2 once and you understand isolation everywhere.

4.4 What each role sees and does

The four panels below summarise the day-to-day surface of each tier. Each maps to a distinct console; an account only ever lands in the one console its type permits.

Platform Admin

The widest console. Creates resellers and direct tenants; manages shared platform credentials; sets platform security policy; drives any supervisor control (monitor / whisper / barge) against any live call. Sees every reseller, tenant, user and CDR. This is the role this manual is written for.

Reseller

A scoped, white-label control plane. Creates and manages its own customers (tenants), sets branding and wholesale plans, and builds a small team of reseller and agent logins. Never sees another reseller’s tenants, and cannot create admin, subtenant or tl logins.

Tenant & Subtenant

The customer’s own administration. Manages contacts, queues, channels, agents and Supervisors within the one tenant. A subtenant is a delegated admin for a department inside the same tenant — same data boundary, narrower remit.

Supervisor (tl) & Agent

Supervisor gets the live-ops wallboard and quality tools — presence, live calls, monitor/whisper/barge, QA scoring. Agent gets only their own work: handle conversations and calls, set presence, view their own queue. Both consume a licensed seat.

CloudCX · Administrator & Training ManualChapter 4 · Roles & the access model
CloudCXCloudCX Administrator & Training Manual
Ch. 4 · Roles & Access

4.5 Where roles live in the Admin console

As Platform Admin you manage tenancy from the dark CloudCX console. The two places roles and tenancy converge are the Tenancy nav group (Resellers and Tenants) and the per-tenant Agents view, where Supervisor and Agent logins are provisioned. The figure below highlights the navigation that frames this whole chapter.

admin.cloudcx.app
Tenants// 86 customer accounts
PRODAO
Tenancy

Tenants

Every customer account on the platform, across all resellers.

+ Add tenant
TenantResellerSeatsStatus
NBNorthwind BanknorthwindAcme Comms42 active
HCHelios Carehelios Direct18 active
OTOrbit TravelorbitAcme Comms7 inactive
1
2
3
4
5
  1. Resellers — the white-label tier. Each reseller owns its own tenants; the badge is the reseller count.
  2. Tenants (active) — every customer account on the platform; the badge is the total tenant count.
  3. Add tenant — opens the create-tenant flow, which also bootstraps the tenant’s primary tenant login (§ 4.6).
  4. Reseller column — which reseller owns each tenant; Direct means a platform customer (reseller_id is NULL).
  5. Seats — the count of agent + tl logins in that tenant; this is what licensing meters.
The Tenancy section of the Admin console — resellers, tenants and the seat-bearing roles they contain.
Read the environment chip

The green PROD chip in the top bar tells you which environment you are operating. Tenancy changes here are live and immediately affect customers. When you are training or rehearsing, confirm you are on a non-production environment first.

CloudCX · Administrator & Training ManualChapter 4 · Roles & the access model
CloudCXCloudCX Administrator & Training Manual
Ch. 4 · Roles & Access

4.6 Provisioning a tenant and its first login

Creating a tenant does two things at once: it creates the Tenant container, and it bootstraps that tenant’s first user — a tenant-type login that becomes the customer’s account owner. From there the customer (or you) creates Supervisors and Agents inside that tenant.

Tenants Add tenant

  1. Open the create-tenant dialog. In the Admin console go to Tenancy › Tenants and click Add tenant (marker 3 in Figure 4.2).
  2. Enter the tenant identity. Provide the display name (e.g. “Helios Care”), a unique username (the login handle and tenant slug), and a contact email.
  3. Set the owner’s password. Type an initial password. The platform applies the active password policy — a weak password is rejected with a validation error before anything is saved.
  4. (Optional) Set a dial prefix. If this tenant’s outbound calls need a leading prefix, set it here; otherwise leave it blank.
  5. (Optional) Assign to a reseller / plan. Leave the reseller unset for a direct customer, or pick the owning reseller for a white-label account, and choose a billing plan if one applies.
  6. Create. Confirm. CloudCX creates the Tenant and, in the same step, a tenant-type User with the username and password you supplied — the account owner.
  7. Verify isolation. Sign in (or have the customer sign in) at cloudcx.app/app with that username. The new login should see an empty-but-its-own tenant — never any other tenant’s data.
Usernames are unique within a tenant

A tenant’s slug-style username is globally unique, but a user’s username only has to be unique inside its own tenant. Two different tenants can each have a user called support without collision — the platform keys on the (tenant, username) pair. Plan your naming with this in mind.

Add tenant

×
Display nameHelios Care
Username (slug)heliosUsed as the tenant handle and the owner’s login.
Contact email[email protected]
Owner password••••••••••••Must satisfy the platform password policy.
ResellerDirect (none)
Dial prefixoptional
Cancel Create tenant
  1. Creating the tenant also creates a tenant-type owner login from the username + owner password — one step, two records.
  2. Leaving Reseller as Direct makes a platform customer; choosing a reseller files the tenant under that partner’s scope.
The create-tenant dialog. The owner login is provisioned automatically as a tenant user.
CloudCX · Administrator & Training ManualChapter 4 · Roles & the access model
CloudCXCloudCX Administrator & Training Manual
Ch. 4 · Roles & Access

4.7 Supervisors and Agents — the seat-bearing roles

Inside a tenant, the working population is made of Agents (agent) and Supervisors (tl). These are the two types that consume a licensed seat: the platform counts every agent + tl login in a tenant against that tenant’s entitlement. The screen below is where you review and add them.

admin.cloudcx.app · Helios Care
Agents & Supervisors// Helios Care · 18 of 25 seats
PRODAO
Tenant · Helios Care

Agents & Supervisors

+ Add user
Seats used
18
of 25
Agents
15
type · agent
Supervisors
3
type · tl
Locked
1
needs review
NameUsernameRoleStatus
MAMara Adlermara Supervisor (tl) active
DIDev Iqbaldev.i Agent active
RKRae Kimrae.k Agent locked
1
2
3
4
5
  1. Seats used / cap — the live agent + tl count against the tenant’s entitlement; creation is blocked once the cap is reached.
  2. Agents tileagent logins: front-line, see only their own work.
  3. Supervisors tiletl logins: live ops & quality tools for the tenant.
  4. Role pill — the user’s type, set at creation and not editable in place.
  5. Status pillactive, inactive or locked; a non-active user cannot sign in (§ 4.9).
The per-tenant Agents view, showing the seat counter and the Supervisor / Agent roster.

4.7.1 Adding a Supervisor or Agent

  1. Pick the tenant. Open the tenant from Tenancy › Tenants, then open its Agents view.
  2. Click Add user. Choose the role: Agent (agent) or Supervisor (tl).
  3. Enter the person’s details. Username (unique within this tenant), optional name and email, and an initial password that meets the policy.
  4. Confirm against the seat cap. If the tenant is at its seat limit the create is blocked — raise the entitlement (Chapter 6) or deactivate an unused login first.
  5. Save, then verify the lens. A new Agent should land in the agent console seeing only their own queue; a new Supervisor should reach the supervisor wallboard for this tenant only.
CloudCX · Administrator & Training ManualChapter 4 · Roles & the access model
CloudCXCloudCX Administrator & Training Manual
Ch. 4 · Roles & Access

4.8 The Supervisor’s elevated powers

The tl / Supervisor role does more than “see more rows” — it carries live-call privileges that ordinary agents do not. On a live call a Supervisor (or the Platform Admin) can step in three escalating ways:

Monitor
listen-only
Silently listen to a live call. Neither party hears the Supervisor. Used for QA and coaching prep.
Whisper
coach
Speak only to the agent — the customer cannot hear it. Used to coach an agent through a live interaction.
Barge
three-way
Join the call audibly to both parties. Used to take over or assist directly.

These controls always respect tenant scope: a Supervisor only sees — and may only act on — live calls belonging to their own tenant. A reseller Supervisor view is scoped to that reseller’s tenants; the Platform Admin can act on any call. Agents have none of these powers.

Monitoring is a privileged, audited action

Listening to, whispering on, or barging into a customer’s live conversation is sensitive. CloudCX records every monitor / whisper / barge action. Ensure your organisation’s call-recording and supervision notices are in place before granting Supervisor seats — the capability is real and immediate.

4.9 Account status — the second gate

Type decides what a login may do; status decides whether it may sign in at all. Every user carries one of three statuses, checked at login and on every authenticated request:

active

Normal. The login works and is subject to its role’s scope.

inactive

Disabled by an admin. Sign-in is refused; data is retained. Reversible.

locked

Blocked (e.g. after security review). Sign-in is refused until unlocked.

Deactivate, don’t delete

To off-board a person, set their login to inactive rather than deleting it. The session is refused immediately (an existing token stops working on its next request), the seat is freed, and the audit trail and any owned records stay intact. Deletion is irreversible and breaks historical attribution.

4.10 A note on reseller teams

A reseller builds a small staff team in its own portal. Crucially, a reseller may only create two kinds of team login: another reseller operator, or an agent. A reseller can never mint admin, tenant, subtenant or tl logins, and every tenant it creates is force-stamped with its own reseller_id — there is no way for a reseller to reach across to another partner’s customers. This is the same isolation rule from § 4.3, applied at the creation step.

Self-service 2FA applies to every role

Independently of type, any login can enrol opt-in two-factor authentication (TOTP authenticator app, or email one-time code) from its own account-security settings. 2FA is off by default and does not change the scope rules — it only hardens sign-in. As Platform Admin you should enable it on your own admin account first (see Chapter 3).

CloudCX · Administrator & Training ManualChapter 4 · Roles & the access model
CloudCXCloudCX Administrator & Training Manual
Ch. 4 · Roles & Access

4.11 Worked example — stand up “Helios Care” end to end

Let us provision a complete, isolated tenant from nothing: a direct customer with one Supervisor and two Agents, and prove the isolation holds.

Scenario

Tenant
Helios Care (direct — no reseller)
Owner login
helios · type tenant
Supervisor
mara · type tl
Agents
dev.i, rae.k · type agent
  1. Create the tenant. Tenancy › Tenants › Add tenant. Name “Helios Care”, username helios, email [email protected], a policy-compliant owner password, Reseller = Direct. Create. → You now have the Tenant and a tenant owner login helios.
  2. Add the Supervisor. Open Helios Care → AgentsAdd user. Role Supervisor, username mara, set a password. → Seats used ticks to 1.
  3. Add two Agents. Repeat Add user twice with role Agent: dev.i and rae.k. → Seats used ticks to 3; the KPI tiles read 2 Agents, 1 Supervisor.
  4. Prove agent isolation. Sign in at cloudcx.app/agent as dev.i. Confirm Dev sees only Helios queues — no other tenant’s conversations — and can set presence but has no monitor / whisper / barge controls.
  5. Prove supervisor scope. Sign in at cloudcx.app/supervisor as mara. Confirm Mara sees the Helios wallboard (live calls, presence, open chats for Helios only) and can monitor a Helios call — but no other tenant’s.
  6. Prove the IDOR guard. While signed in as dev.i, attempt to open a record id that belongs to a different tenant. Expect 404 Not Found, exactly as if the record did not exist (§ 4.3).
  7. Off-board cleanly. Set rae.k to inactive. Confirm Rae can no longer sign in and that Seats used drops back to 2, freeing the seat without deleting history.
Expected end state

One tenant (Helios Care), four logins (1 tenant owner + 1 tl + 2 agents, one of which is inactive), 2 active seats, and three proofs on record: agents are blind beyond their own work, the Supervisor is scoped to Helios, and cross-tenant lookups return 404. That is the entire access model, demonstrated.

Try it — reason about the boundaries

Exercise 4-A. Using a training/non-production environment, complete steps 1–7 above for a tenant of your own naming. Then answer in your own words:

4.12 Chapter recap

CloudCX · Administrator & Training ManualChapter 4 · Roles & the access model
CloudCXCloudCX Administrator & Training Manual
Ch. 5 · Resellers
Chapter 05

Resellers — create, brand, entitle, white-label

A reseller is the white-label partner tier that sits between you (the platform) and the enterprise customers who actually use CloudCX. A reseller resells the platform under its own brand, sells to its own customers (tenants), prices them with its own plans, and — where you permit it — connects its own carriers and channel providers. This chapter shows you how to create a reseller, brand it, set its entitlement caps, switch on its “bring-your-own” connectivity, and what the partner then sees inside their own white-label console.

Where you do this work

Everything in this chapter happens in the platform admin console at admin.cloudcx.app under the TenancyResellers view. The partner’s own self-service console — covered at the end of the chapter — lives at a separate address: cloudcx.app/reseller.

5.1The reseller in the tenancy hierarchy

CloudCX is a strict three-level tenancy: Platform Admin → Reseller → Tenant. You operate the platform; a reseller operates a book of business on top of it; each tenant is one enterprise customer with its own agents, numbers and channels. Every object a reseller can touch is scoped to its own reseller_id, so one partner can never see or modify another partner’s brand, customers or billing.

The three-tier tenancy and what each level owns
Platform AdminYou. Operate CloudCX; create resellers; set their caps and bring-your-own toggles; own the shared connectivity and wholesale rate cards.admin.cloudcx.app
ResellerWhite-label partner. Own brand & login domain; sells to its own customers (tenants); its own plans & margin; may bring its own carriers/providers where you allow it.cloudcx.app/reseller
TenantOne enterprise customer of the reseller. Its agents, queues, DIDs and channels live here. The end users experience the reseller’s brand.tenant console

A reseller record carries four kinds of data. Keep them straight — the rest of the chapter is organised around them:

Identity

Company name, the primary login username, and the admin email. The username is unique platform-wide and indexes the partner.

Branding

Brand name, logo, two brand colours and a unique login domain — the white-label face the partner’s customers see.

Entitlements

Caps on customers, agents and channels (0 = unlimited), plus three bring-your-own toggles (SIP / SMS / WhatsApp).

Wholesale

The currency and per-seat price at which you bill the partner. Drives the wholesale ceiling shown on the reseller’s drawer.

Tip · 0 means unlimited

Throughout the platform, a cap of 0 means unlimited, not “none”. A reseller with max_customers = 0 can create as many tenants as it likes. Set a real number whenever you want a hard ceiling enforced.

CloudCX · Administrator & Training ManualCh. 5 · Resellers
CloudCXCloudCX Administrator & Training Manual
Ch. 5 · Resellers

5.2The Resellers list

Open Resellers from the Tenancy group in the left navigation. The list is the home for every white-label partner on the platform. Each row summarises one reseller; click a row to open its entitlements drawer (§ 5.5). The toolbar above the table lets you filter by status and search by name or domain.

admin.cloudcx.app/#resellers
Operate
Dashboard
Reports
Tenancy
Resellers12
Tenants86
Revenue
Billing
Licensing
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Resellers// white-label partners · entitlements
PRODAO
// 02 — White-label partners

Resellers

White-label partners running CloudCX under their own brand. Click a row to manage entitlements and SIP.

+ Add reseller
All Active Inactive Locked Filter by name or domain… 12 partners
ResellerBrand domainCustomers (cap)Agents (licensed)BYO-SIPStatus
NTNimbus TelecomNimbus CX cx.nimbustel.com 14 / 50500 On Active Manage ›
ACAcme CommsAcme Connect — not set — — / 25120 Off Active Manage ›
OROrbit VoiceOrbit talk.orbit.io — / ∞ On Inactive Manage ›
1
2
3
4
5
  1. Resellers — the active item in the Tenancy nav group; the badge shows the partner count.
  2. Add reseller — opens the inline New reseller form (§ 5.3).
  3. Status filter & search — segment by All / Active / Inactive / Locked, or filter the table by name or domain.
  4. Customers (cap) — live customer count over the cap. A dash () means the live count is not on the list payload; means an unlimited cap (0).
  5. BYO-SIP — a quick read of each partner’s Allow own SIP trunks entitlement; toggle the full set in the drawer.
The Resellers list, with the create action and key columns highlighted.
Note · Why some counts show a dash

The list endpoint returns the reseller records themselves, not a per-partner live tenant count, so the Customers column renders — / cap rather than fabricating a zero. The true live usage (“12 / 50”) is computed inside the partner’s own console and on the Licensing page.

CloudCX · Administrator & Training ManualCh. 5 · Resellers
CloudCXCloudCX Administrator & Training Manual
Ch. 5 · Resellers

5.3Creating a reseller

Click Add reseller to reveal the New reseller form inline at the top of the list. The form is split into two columns: identity & branding on the left, caps & connectivity on the right. Only three fields are mandatory (marked *); everything else has a sensible default you can refine later from the entitlements drawer.

admin.cloudcx.app/#resellers
Tenancy
Resellers12
Tenants86
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Resellers// new reseller
PRODAO

New reseller

draft
Reseller name *Nimbus Telecom
Login username *nimbus_admin
Admin email *[email protected]
Brand nameNimbus CX
Login domaincx.nimbustel.com
Max customers50
Max agents (licensed)500
Max channels6
Allow own SIP trunks
Reseller can connect their own carriers
Cancel Create reseller
1
2
3
4
5
6
  1. Reseller name * — the company’s legal/display name (e.g. Nimbus Telecom).
  2. Login username * — the unique platform-wide handle for the partner; also the default login when you bootstrap their admin later.
  3. Admin email * — the partner’s primary contact / login email.
  4. Branding fields — optional Brand name and Login domain set up the white-label face (§ 5.4).
  5. Caps & Allow own SIP trunks — the starting entitlements: customer / agent / channel caps and the BYO-SIP toggle.
  6. Create reseller — validates and saves; the new partner appears at the top of the list.
The New reseller form — identity and branding on the left, caps and connectivity on the right.

5.3.1Step by step

  1. Open the form. On the Resellers list, click Add reseller. The New reseller panel expands inline.
  2. Enter identity. Fill Reseller name, a unique Login username (3–128 chars), and a valid Admin email. These three are required.
  3. Add branding (optional). Set the Brand name and a Login domain such as cx.nimbustel.com. You can leave both blank now and brand later from the drawer.
  4. Set the caps. Choose Max customers (default 50), Max agents (licensed) (default 500) and Max channels (default 6, the platform’s channel maximum). Use 0 for any cap you want unlimited.
  5. Decide BYO-SIP. Leave Allow own SIP trunks off unless this partner brings its own carrier; you can flip it any time in the drawer.
  6. Create. Click Create reseller. On success the partner is added to the top of the list. A duplicate username or login domain returns a conflict — pick another value.
Warning · Username and login domain are unique

Both the login username and the login domain are unique platform-wide. Creating a reseller whose username or domain collides with an existing one is rejected with a 409 Conflict (“username or login domain already exists”). Choose distinctive values — a partner’s domain is hard to change once their customers are using it.

Note · No password on this form — by design

The create form deliberately does not collect a reseller-admin password. The reseller record is created on its own; its primary reseller login is bootstrapped separately (§ 5.6). When a password is later supplied, it must satisfy the platform password policy or the request is rejected with 422.

CloudCX · Administrator & Training ManualCh. 5 · Resellers
CloudCXCloudCX Administrator & Training Manual
Ch. 5 · Resellers

5.3.2New-reseller field reference

Every field the create form accepts, its constraint, and what it controls downstream:

Reseller namename
Required. 1–255 chars. The partner’s display name throughout the admin console.
Login usernameusername
Required. 3–128 chars, unique platform-wide. The partner’s handle and the default login when bootstrapping their admin.
Admin emailemail
Required. A valid email. The partner’s primary contact / login address.
Brand namebrand_name
Optional. Up to 255 chars. The product name the partner’s customers see (e.g. Nimbus CX).
Login domainlogin_domain
Optional, unique. The white-label host the partner’s users sign in at (e.g. cx.nimbustel.com).
Max customersmax_customers
Cap on the number of tenants the partner may create. 0 = unlimited. Default 50. Enforced live when the partner adds a customer.
Max agentsmax_agents
Licensed agent-seat cap across the partner’s customers. 0 = unlimited. Default 500.
Max channelsmax_channels
Number of channel types the partner may light up. 0 = unlimited; the platform maximum is 6.
Allow own SIP trunksallow_own_sip_trunk
Bring-your-own gate for voice carriers. Off by default. See § 5.6.

Three further fields are part of the reseller record but are set from the entitlements drawer or via the API rather than this form:

FieldAPI keyDefaultPurpose
Allow own SMSallow_own_smsoffBYO gate for the SMS provider (Twilio-style creds).
Allow own WhatsAppallow_own_whatsappoffBYO gate for WhatsApp (Meta Cloud API creds).
Wholesale currencywholesale_currencyUSDCurrency you bill the partner in.
Wholesale seat pricewholesale_seat_price0.00Per-agent / month wholesale rate; drives the wholesale ceiling on the drawer.
Table 5.1 — Reseller fields not on the quick-create form.
Tip · Brand later if you’re in a hurry

If you just need the partner record to exist (for example to start importing customers), create it with only the three required fields. You can return to the entitlements drawer at any time to add branding, raise caps and switch on bring-your-own connectivity — nothing on the create form is permanent except the username.

CloudCX · Administrator & Training ManualCh. 5 · Resellers
CloudCXCloudCX Administrator & Training Manual
Ch. 5 · Resellers

5.4Branding & white-label

Branding is what turns a generic CloudCX instance into the partner’s product. Five fields make up the white-label identity. You can seed them on the create form; the partner can also refine the visual ones from inside their own console. Only the platform admin can be sure of the login domain being globally unique, so treat that field as something you own jointly with the partner.

brand_name
The product name shown to the partner’s customers
logo_url
URL of the partner’s logo (replaces the CloudCX mark)
primary_color
Hex accent, e.g. #1E6FFF
secondary_color
Hex secondary accent, e.g. #0B1F44
login_domain
Unique sign-in host, e.g. cx.nimbustel.com

The partner’s own console exposes exactly these as an editable branding panel. Here is the partner-side view so you know what they will see when they finish the white-label:

cx.nimbustel.com/reseller/#branding
Brand
Branding
Customers14
Plans
BRANDING PUBLISHED
cx.nimbustel.com
Branding// your white-label identity
LIVENT

White-label branding

published
Brand nameNimbus CX
Logo URLhttps://nimbustel.com/logo.svg
Login domaincx.nimbustel.comWhere your customers sign in. Unique across CloudCX.
Primary colour#1E6FFF
Secondary colour#0B1F44
PreviewPublish branding
5
1
2
3
4
  1. Brand name — the product name customers see in titles, emails and the sign-in card.
  2. Login domain — the white-label host. Unique platform-wide; this is what makes the console “theirs”.
  3. Primary colour — the accent applied to buttons, links and highlights.
  4. Secondary colour — the deeper companion colour for headers and chrome.
  5. Published state — once published, the sidebar logo and footer reflect the partner’s identity.
The partner-side branding panel — the same five fields you seed from the admin console.
Note · DNS & TLS for the login domain

Setting login_domain on the record only tells CloudCX which host belongs to which partner. For the domain to actually serve, its DNS must point at the platform and a TLS certificate must cover it. Coordinate the CNAME and certificate with the partner before you promise them a go-live date for the branded URL.

CloudCX · Administrator & Training ManualCh. 5 · Resellers
CloudCXCloudCX Administrator & Training Manual
Ch. 5 · Resellers

5.5The entitlements drawer

Click any reseller row to slide out the entitlements drawer. This is where you do nearly all day-two reseller management: review the partner at a glance, adjust the licensed caps, flip the three bring-your-own toggles, and see the wholesale position. Changes to the caps are saved with Save entitlements; the three BYO toggles save immediately as you flip them.

admin.cloudcx.app/#resellers
// reseller · entitlements
NT

Nimbus Telecom

cx.nimbustel.com
Cust. cap
50
Seat cap
500
Status
Active
Limits & entitlements
Max customers · 0 = unlimited50
Max agents (licensed) · 0 = unlimited500
Max channels6
Channel connectivity · bring-your-own
Allow own SIP trunks
CURRENTLY: ENABLED
Allow own SMS
CURRENTLY: DISABLED
Allow own WhatsApp
CURRENTLY: DISABLED
Billing
Wholesale rate / agent / moUSD 12.00
Wholesale ceilingUSD 6,000
CloseSave entitlements
1
2
3
4
5
  1. At-a-glance header — the partner’s avatar, name and login domain, with cap/seat/status KPIs.
  2. Limits & entitlements — editable Max customers / agents / channels (0 = unlimited).
  3. Bring-your-own toggles — SIP / SMS / WhatsApp, each showing CURRENTLY: ENABLED/DISABLED; these save the moment you flip them.
  4. Wholesale ceiling — seat cap × wholesale rate / month: the maximum you could bill the partner.
  5. Save entitlements — persists the cap edits (the toggles have already saved on flip).
The reseller entitlements drawer, where caps, BYO connectivity and wholesale are managed.
Tip · Caps are enforced live

When a partner tries to add a customer beyond max_customers, the platform rejects it with “customer limit reached for this reseller.” The check combines the stored cap with a live count of the partner’s tenants, so raising the cap in this drawer takes effect immediately — no re-provisioning.

CloudCX · Administrator & Training ManualCh. 5 · Resellers
CloudCXCloudCX Administrator & Training Manual
Ch. 5 · Resellers

5.6Bring-your-own connectivity (allow-own-credentials)

By default every channel a partner uses runs on CloudCX’s shared platform credentials — your carriers, your SMS account, your WhatsApp number. The three allow-own toggles let a partner instead supply its own provider credentials for that channel, so traffic flows over the partner’s own accounts and appears under the partner’s own sender identities. You hold the master switch; the partner enters the actual secrets in their console.

ToggleEntitlement flagChannelWhat the partner then supplies
Allow own SIP trunksallow_own_sip_trunkVoiceIts own carrier trunk(s): host, username, password (+ optional proxy / port).
Allow own SMSallow_own_smsSMSIts own SMS provider keys: account_sid, auth_token, from_number.
Allow own WhatsAppallow_own_whatsappWhatsAppIts own Meta Cloud API creds: token, phone_id, verify_token.
Table 5.2 — The three bring-your-own entitlements and their credential fields.

5.6.1How resolution works

For any send, CloudCX resolves the effective credentials for the channel in a fixed order. A channel uses the partner’s own credentials only when it is both allowed and configured; otherwise it “binds to CloudCX” and falls back to the shared platform credentials.

Credential resolution order for a reseller’s channel
Allowed?allow_own_<ch>
Configured?stored creds row
Reseller’s ownuse partner creds
Otherwisenot both
CloudCX sharedplatform creds
Nothing setchannel idle (no-op)
Warning · Turning a toggle OFF does not delete the partner’s secrets

Switching allow_own_sms (or whatsapp / sip) off immediately stops resolution from using the partner’s stored credentials — the channel re-binds to CloudCX’s shared creds on the very next send — but the encrypted credential row is not erased. Flip the toggle back on and the partner’s own connectivity resumes. To remove the secrets entirely, the partner must delete them from their console (or you rotate the platform key).

Note · Secrets are encrypted, never shown

Partner credentials are encrypted at rest (Fernet) and are never returned by any API — the console only ever reports whether a channel is allowed and configured, plus the field names it expects. The single root key, BYOND_CREDS_KEY, lives in the server environment, not the database. If that key is missing the platform refuses to store new secrets (returning 503) rather than persist anything in plaintext.

CloudCX · Administrator & Training ManualCh. 5 · Resellers
CloudCXCloudCX Administrator & Training Manual
Ch. 5 · Resellers

5.7The white-label partner console

Once a partner has a login (§ 5.8) it signs in to its own self-service console at cloudcx.app/reseller — or, after the login domain is published, at the partner’s own host such as cx.nimbustel.com. Everything the partner sees there is scoped strictly to its own reseller_id; it can never reach another partner’s data or any platform-admin function. Knowing this surface helps you support partners and understand exactly where your admin-side toggles land.

5.7.1What the partner can do

Branding

Self-edit brand name, logo, the two colours and (subject to uniqueness) the login domain.

Customers

Create and list its tenants — capped by max_customers, enforced live.

Plans

Build retail billing plans (base + per-seat + per-minute + per-channel pricing).

SIP trunks

Self-provision carrier trunks — only when allow_own_sip_trunk is on.

Channel credentials

Enter its own SMS / WhatsApp / SIP provider secrets — only for the channels you allowed.

Team & reports

Add reseller-admin / agent team logins, view its entitlement usage, and export reseller-scoped CSV reports.

The partner’s “bring-your-own” credentials panel mirrors your toggles exactly: a channel you left off appears locked, while a channel you enabled becomes editable. Here is that partner-side panel:

cx.nimbustel.com/reseller/#credentials
Connectivity
SIP trunks
Credentials
BRANDING PUBLISHED
cx.nimbustel.com
Channel credentials// bring your own providers
LIVENT

SMS · Twilio

allowed
account_sidACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
auth_token•••••••••• (write-only)
from_number+1 415 555 0142
RemoveSave SMS creds

WhatsApp · Meta Cloud API

managed by CloudCX
This channel is managed by your platform provider (bound to CloudCX). Ask your provider to enable allow_own_whatsapp to bring your own.
1
2
3
  1. Allowed channel — SMS is marked allowed, so the partner can enter its own provider fields.
  2. Write-only secret — secret fields like auth_token are masked and never echoed back.
  3. Locked channel — WhatsApp shows managed by CloudCX because allow_own_whatsapp is off; the partner cannot enter creds until you enable it.
The partner’s bring-your-own credentials panel — your admin toggles decide which channels are editable.
CloudCX · Administrator & Training ManualCh. 5 · Resellers
CloudCXCloudCX Administrator & Training Manual
Ch. 5 · Resellers

5.8Worked example — onboarding “Nimbus Telecom”

Let’s onboard a real partner end-to-end. Nimbus Telecom is a regional carrier that wants to resell CloudCX as Nimbus CX at cx.nimbustel.com. They will bring their own SIP carrier and their own Twilio SMS account, but use CloudCX’s shared WhatsApp to start. We license them for up to 25 customers and 250 agent seats at a wholesale rate of USD 12 / seat / month.

Partner
Nimbus Telecom → brand Nimbus CX
Login domain
cx.nimbustel.com
Caps
25 customers · 250 seats · 6 channels
Bring-your-own
SIP ✓ · SMS ✓ · WhatsApp ✗ (CloudCX)
  1. Create the partner record. Resellers → Add reseller. Name Nimbus Telecom, username nimbus_admin, email [email protected]. Brand Nimbus CX, login domain cx.nimbustel.com. Set Max customers 25, Max agents 250, Max channels 6. Leave Allow own SIP trunks off for now. Click Create reseller.
  2. Set the wholesale rate. Open the new partner’s drawer and confirm the wholesale currency is USD and the seat price is 12.00. The wholesale ceiling now reads USD 3,000 (250 seats × 12). Click Save entitlements.
  3. Enable bring-your-own SIP and SMS. Still in the drawer, flip Allow own SIP trunks and Allow own SMS on (each saves on flip and shows CURRENTLY: ENABLED). Leave Allow own WhatsApp off so WhatsApp stays bound to CloudCX.
  4. Bootstrap the partner login. Provision the primary reseller-admin login (§ 5.8.1) so Nimbus can sign in. Choose a strong password — it must pass the platform password policy.
  5. Hand off to the partner. Nimbus signs in at cloudcx.app/reseller, finishes branding (logo + the two colours), enters its SIP trunk and Twilio SMS credentials, then creates its first customer (tenant). WhatsApp keeps using CloudCX until you enable BYO for it.
  6. Verify. Back in the admin list, the Nimbus row shows BYO-SIP On and 25 as the customer cap. As Nimbus adds customers, the live count climbs toward the cap; at 25 the next create is refused.

5.8.1Bootstrapping the reseller login

The quick-create form intentionally omits a password, so the partner record initially has no login. Provision the primary reseller-admin user with a single API call (the password is enforced against the platform policy):

Bootstrap a reseller-admin login (platform-admin token required)
# Provision the primary reseller-admin login for an existing partner.
# A 422 means the password failed the platform password policy.
curl -X POST https://cloudcx.app/api/v1/resellers \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "Nimbus Telecom",
        "username": "nimbus_admin",
        "email": "[email protected]",
        "admin_username": "nimbus_admin",
        "admin_password": "S3tA-Strong-Passphrase!",
        "brand_name": "Nimbus CX",
        "login_domain": "cx.nimbustel.com",
        "max_customers": 25,
        "max_agents": 250,
        "max_channels": 6,
        "allow_own_sip_trunk": true,
        "allow_own_sms": true
      }'
Note · Include the password to create the login in one shot

When the create payload carries admin_password (and optionally admin_username, which defaults to the reseller’s username), CloudCX bootstraps the primary reseller-type login alongside the record — the partner can sign in immediately. Omit it (as the UI form does) and provision the login later. Either way the password must satisfy the platform password policy.

CloudCX · Administrator & Training ManualCh. 5 · Resellers
CloudCXCloudCX Administrator & Training Manual
Ch. 5 · Resellers

5.9Practice & recap

Try it · Onboard a practice partner

In a non-production environment, onboard a fictional partner and verify each control end-to-end:

  1. Create a reseller Orbit Voice (brand Orbit) with Max customers = 3 and Max agents = 10. Leave all bring-your-own toggles off.
  2. Open its drawer and confirm the wholesale ceiling updates when you change the seat price.
  3. Enable Allow own SMS only. Check the row’s BYO indicators and that CURRENTLY: ENABLED shows on the SMS toggle.
  4. Bootstrap a login and sign in to cloudcx.app/reseller. Confirm SMS is editable while WhatsApp and SIP show managed by CloudCX.
  5. As the partner, create customers until you hit the cap of 3 — the 4th create must be refused with a customer-limit error.
  6. Back in admin, turn Allow own SMS off again. Confirm the partner’s SMS panel locks but its previously entered creds are not required to be re-entered when you turn it back on.

Success looks like: caps enforced live, toggles reflected on both sides, and secrets never displayed.

Tip · A clean onboarding checklist

For every new partner, in order: (1) create record + brand, (2) set caps, (3) set wholesale rate, (4) enable only the bring-your-own channels they need, (5) bootstrap the login, (6) hand off and confirm their first customer is created.

5.9.1Chapter recap

Note · Where to go next

With a partner onboarded, the natural next step is to populate it with customers. Chapter 6 — Tenants covers creating and configuring the enterprise customers that sit under a reseller; the partner’s retail plans and your wholesale rate cards are covered in Chapter 23 — Billing.

CloudCX · Administrator & Training ManualCh. 5 · Resellers
CloudCXCloudCX Administrator & Training Manual
Ch. 06 · Tenants
Chapter 06

Tenants — create, configure, license

A tenant is a single business customer running its own contact centre on CloudCX — its own agents, numbers, queues, channels and data, fully isolated from every other tenant. This chapter shows you how to create a tenant from the platform console, configure its core identity, understand how seat and feature limits are licensed and enforced, and read the live entitlement counters. It closes with a fully worked provisioning example and a hands-on Try-it exercise.

Where tenants sit in the hierarchy

CloudCX is a three-tier platform: Platform Admin → Reseller → Tenant. A tenant can be owned by a reseller (white-label tier) or be a platform-direct customer with no reseller (reseller_id = NULL). Licensing caps — how many customers and agent seats are allowed — live on the reseller, so a tenant inherits its room-to-grow from its parent. Platform-direct tenants are uncapped today. Resellers themselves are covered in Chapter 5; this chapter is about the tenants beneath them.

6.1 The Tenants workspace

Open the console at admin.cloudcx.app, sign in with your platform-administrator account, and select Tenants in the dark sidebar under the Tenancy group. This is the platform-wide book of every enterprise customer — those belonging to resellers and those you sell to directly. The list is loaded live from GET /api/v1/tenants and ordered newest-first.

Use the breadcrumb chip below as your map for the procedures in this chapter:

TenancyTenantsAdd tenant

admin.cloudcx.app
Operate
Dashboard
Reports
Tenancy
Resellers12
Tenants86
Revenue
Billing
Licensing
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Tenants// 86 enterprise customers · all resellers
PRODAO
Tenancy · 03

Tenants

Every enterprise customer across all resellers and the platform-direct book.

Refresh+ Add tenant

All customers

AllActiveInactiveLocked
TenantUsernameEmailDial prefixStatus
NGNorthgate Banktenantnorthgate[email protected]9 Active
ACAcme Retailtenantacme-retail[email protected] Active
BVBluewave Telcotenantbluewave[email protected]7 Inactive
HMHelix Medicaltenanthelix-med[email protected] Locked
1
2
3
4
5
  1. Tenants nav item (Tenancy group) — the active section; the badge shows the live tenant count.
  2. Title & data source — the running count and the // live · /api/v1/tenants source line confirm the table is live, not demo data.
  3. Add tenant — opens the create-tenant form (§ 6.2). Refresh re-pulls the list.
  4. Status filter — All / Active / Inactive / Locked, plus a free-text filter over name, username and email.
  5. Tenant rows — name, login username, contact email, outbound dial prefix and lifecycle status pill.
The Tenants workspace: the platform-wide book of every enterprise customer.
Read the data-source line

Every live panel in the CloudCX console prints a small mono source line (for example // live · /api/v1/tenants or // live · no tenants yet). If it instead reads // data unavailable or you see a "Couldn’t load tenants" toast, you are looking at a connectivity problem — not an empty platform. Treat the source line as ground truth before acting.

6.2 Creating a tenant

Click Add tenant to open the create form. A tenant needs only four things to come to life: a display name, a unique login username, a contact email, and the password for its first login. An optional dial prefix can be set now or later. When you save, CloudCX does two things in one transaction: it creates the tenant record, and it bootstraps the tenant’s primary login (a user of type tenant) using the username, email and password you supplied.

Add tenant

Company name Northwind Logistics
Login username northwind-log Unique across the whole platform · 3–128 chars · this is the sign-in name.
Contact email [email protected]
Initial password •••••••••••• Sets the first login’s password · must satisfy the platform password policy.
Dial prefix (optional) e.g. 9 — prepended to outbound calls
1
2
3
Cancel Create tenant
  1. Login username — must be unique across the entire platform. A clash returns a clear "Tenant username already exists" error and nothing is saved.
  2. Initial password — this becomes the password of the tenant’s first user; it is validated against the platform password policy before the tenant is created.
  3. Dial prefix — optional outbound prefix (up to 16 chars) prepended to this tenant’s calls; safe to leave blank and add later.
The Add-tenant form. Save creates the tenant and its primary login together.

6.2.1 Field reference

The create form maps directly onto the POST /api/v1/tenants request body. Each field, its constraints and how CloudCX uses it:

Company namename
The tenant’s display name shown throughout the console and on invoices. Required, 1–255 characters. Not unique — two customers may legitimately share a trading name.
Login usernameusername
The sign-in handle for the tenant’s primary login and the stable key you will quote in support. Unique platform-wide, 3–128 characters. A duplicate is rejected with 409 Conflict.
Contact emailemail
A valid email address (format-validated). Used as the primary login’s email, for password-reset and verification mail, and as the operational contact.
Initial passwordpassword
Sets the password of the bootstrapped tenant user. 4–128 characters at the schema level, then run through the live platform password policy; a weak value is rejected with 422 before anything is written.
Dial prefixdial_prefix
Optional. Up to 16 characters prepended to every outbound call from this tenant (for example a carrier or break-out digit). Leave empty for none.
One create call, one transaction

The handler validates the password policy, checks the tenant_limit entitlement, inserts the tenants row, then inserts the primary tenant-type users row — all in a single unit of work. If the username collides on flush, the whole thing rolls back and you get a 409, so you never end up with a half-created tenant or an orphan login.

6.2.2 Step by step: create a tenant

  1. Open the form. From TenancyTenants click Add tenant (top-right).
  2. Enter the company name. Type the customer’s display name, e.g. Northwind Logistics.
  3. Choose a unique username. Use a lower-case, hyphenated handle such as northwind-log. If it is already taken anywhere on the platform you will be told on save.
  4. Set the contact email. Enter a real, monitored mailbox — password-reset and verification mail go here.
  5. Set a strong initial password. It must satisfy the platform password policy (see Chapter 3 / Security). Communicate it to the customer over a secure channel.
  6. Optionally set a dial prefix. Add an outbound prefix only if this customer needs one; otherwise leave it blank.
  7. Create. Click Create tenant. On success the tenant appears at the top of the list with status Active and the primary login is live.
  8. Verify. Confirm the new row, then refresh the Tenants badge and the dashboard Tenants KPI — both should increment by one.
The initial password is the keys to the kingdom

The password you type here unlocks the tenant’s primary account — the one that can create that tenant’s agents, queues and channel connections. Never reuse a password across tenants, never send it in clear text, and tell the customer to rotate it on first sign-in. CloudCX never displays it again.

6.2.3 What can go wrong

SymptomHTTPMeaning & fix
Tenant username already exists409The username is taken somewhere on the platform. Pick another; nothing was saved.
Password rejected422The initial password failed the platform password policy. Strengthen it (length / complexity) and retry.
Customer / tenant limit reached409A licensing cap would be exceeded (relevant when the tenant is created under a capped reseller). Raise the cap or remove an inactive tenant first — see § 6.3.
Invalid email422The email failed format validation. Correct it and retry.
Not authorised401/403Only a platform-admin may create tenants here. Sign in with the right role.
Table 6.1 — Create-tenant outcomes and remedies.

6.3 Seat & feature limits — how licensing works

CloudCX gates commercial capacity with a per-count entitlement engine. Every limit is a single number, and the platform follows one golden rule throughout:

The "0 = unlimited" convention
Allotment = 0No cap configured — the feature is unlimited for this scope. Reported honestly as "unlimited", never as a fake number.
Allotment = NA hard cap of N. The action is allowed only while used + requested ≤ N.N
Over capWhen used + requested > N the engine raises LicenseExceeded and the API answers 409 Conflict.409

6.3.1 Where caps live

A scope is the entity a limit applies to. Today, caps are stored on the reseller row, so every tenant inherits its head-room from its parent reseller. Three numeric caps drive tenant capacity:

max_customers
Tenants allowed under the reseller
max_agents
Agent + team-lead seats across its tenants
max_channels
Channel allotment (SMS / chat / email / WhatsApp)
platform scope
No stored caps — unlimited (honest no-op)

The platform scope (a tenant created directly, with no reseller) has no stored caps today, so its limits resolve to unlimited. The enforcement gate is nonetheless wired into the create path, so the moment a platform-level cap is ever stored it will start blocking automatically — no code change required.

Reseller capFeature gatedWhat "used" counts
max_customerstenant_limitNumber of tenants under the reseller.
max_agentsagent_sessionsUsers of type agent + tl across the reseller’s tenants.
max_channelschat · sms · email · whatsappShared channel allotment reused across the per-channel features.
Table 6.2 — The three stored reseller caps and the features they gate.
Set a tenant’s capacity by sizing its reseller

Because seat and customer caps live on the reseller, the way you "license" a tenant for growth is to set the right max_customers and max_agents on its parent reseller (Chapter 5 → Resellers → Entitlements). A platform-direct tenant has no such ceiling — cap it by sizing the platform, or by moving it under a metered reseller.

6.3.2 The full feature catalogue

The engine recognises a broad catalogue of licensable features — mirroring the reference license dashboard — even though only the three caps above are stored as numbers today. The rest resolve to unlimited until a cap is configured, so they never block spuriously. Knowing the catalogue helps you read the entitlement panel and anticipate future metering.

Capacity

reseller · tenant_limit · agent_sessions · live_calls · extensions

Channels

sms · chat · email · whatsapp · social_media

Apps & roles

crm · qa · tl · supervisor · user_roles · ticket

AI & bots

ai_bot · whatsapp_bot · transcript · google_tts

Reporting

report_scheduler · custom_report · survey

Routing & security

advance_routing · voicemail · event_notification · mfa · sso

6.3.3 What happens at the cap

The gate is checked before the work is done. When you (or a reseller) try to add a tenant or seat that would exceed a stored cap, the request is refused cleanly and nothing is created:

Create-tenant entitlement gate
Add tenantPOST /tenants
Password policy422 if weak
tenant_limitused+1 ≤ cap?
Insert tenant
+ primary login
201 Created

If the tenant_limit check fails, the API returns 409 Conflict with a message such as “Customer limit reached for this reseller” (reseller path) or the engine’s detail “License limit reached for ‘tenant_limit’: used N + requested 1 > allotted N”. The same pattern protects agent-seat creation against agent_sessions.

Lowering a cap below current usage

Entitlements are checked at create time, not retroactively. If you reduce max_customers to a number below the tenants already provisioned, those existing tenants keep running — but no new ones can be added until usage drops back under the cap. Reduce caps deliberately, and communicate the freeze to the affected reseller.

6.4 Reading entitlements & usage

CloudCX makes the entitlement engine observable. The platform Licensing view (sidebar → Revenue → Licensing) shows the platform-wide picture; each reseller and tenant portal shows its own scope. Behind it, GET /api/v1/admin/platform/entitlements returns an entitlements summary: headline used counts plus a per-feature row of allotted / used / unlimited / remaining.

admin.cloudcx.app
Tenancy
Resellers12
Tenants86
Revenue
Billing
Licensing
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Licensing// live · /admin/platform/entitlements + /tenants
PRODAO
Revenue

Licensing

Licensed capacity vs. live usage across the platform.

Resellers
12
partners
Licensed agent seats
640
allocated to resellers
Licensed customers
110
tenant capacity
Tenants provisioned
86
live customers

Per-feature entitlements · platform scope

live
FeatureAllottedUsedRemaining
tenant_limitunlimited86
agent_sessionsunlimited512
1
2
3
4
  1. Licensing nav item (Revenue group) — the platform-wide entitlement & usage view.
  2. Licensed capacity KPIs — agent seats and customer capacity summed from reseller caps.
  3. Tenants provisioned — the live tenant count (the headline customers_used).
  4. Per-feature rows — allotted / used / remaining; unlimited rows show no remaining figure.
The platform Licensing view: live usage against licensed capacity.

The summary payload is small and self-describing. Read the per-feature rows like this:

GET /api/v1/admin/platform/entitlements
// platform scope — no stored caps today, so allotted = 0 (unlimited)
{
  "scope": "platform",
  "customers_used": 86,        // live tenant count
  "agents_used": 512,          // agent + tl users, platform-wide
  "entitlements": [
    { "feature":"tenant_limit",    "allotted":0, "used":86,  "unlimited":true,  "remaining":null },
    { "feature":"agent_sessions",  "allotted":0, "used":512, "unlimited":true,  "remaining":null }
  ]
}

For a reseller scope the shape is identical but the numbers bite: allotted carries the stored cap and remaining is allotted − used (never negative). That is how the reseller portal renders headlines like “12 / 50 customers”.

allotted
The cap. 0 = unlimited.
used
Live count from the database.
unlimited
true when allotted is 0.
remaining
allotted − used, or null if unlimited.
Usage is always live, allotment may be unlimited

Even where no cap is stored, the used figures are real database aggregates — total tenants, total agent/team-lead users. So the Licensing view is a trustworthy capacity dashboard today, and it becomes a true headroom gauge the instant you store caps. The per-feature pools for AI sessions, transcript minutes, recording storage and dialer ports are metered at the service layer and will surface here as that metering API ships.

6.5 Worked example — provision “Northwind Logistics”

A reseller, Acme Comms, has just signed a new end-customer, Northwind Logistics, and asked you to stand up their tenant. Acme is licensed for 50 customers and currently has 49. Follow the flow end-to-end.

6.5.1 1 — Check the head-room

  1. Open Licensing. Go to RevenueLicensing and locate Acme Comms in Licensed seats by reseller. Confirm its customer line reads 49 / 50 — exactly one slot free.
  2. Decide. One free slot is enough for one tenant. (If it read 50 / 50, you would first raise Acme’s max_customers in Chapter 5 → Resellers → Entitlements, otherwise the create would 409.)

6.5.2 2 — Create the tenant

Because Northwind belongs to a reseller, the cleanest path is for Acme to create it from its reseller portal (which forces the new tenant under Acme and enforces max_customers automatically). As platform-admin you can also create it here; if you do, remember to set its owning reseller so it counts against Acme’s cap. Either way the field values are the same:

Company name
Northwind Logistics
Username
northwind-log
Dial prefix
9
  1. Fill the form with the values above and a strong initial password, then click Create tenant.
  2. Read the result. A 201 means success: the tenant row and its primary tenant login both exist. The console drops Northwind Logistics at the top of the Tenants list as Active.
POST /api/v1/tenants — 201 Created (response)
{
  "id": "e7c1…a93f",
  "name": "Northwind Logistics",
  "username": "northwind-log",
  "email": "[email protected]",
  "status": "active",
  "dial_prefix": "9",
  "created_at": "2026-06-15T09:42:11Z"
}

6.5.3 3 — Confirm the meters moved

  1. Re-open Licensing. Acme’s customer line now reads 50 / 50 — the create consumed the last slot.
  2. Test the cap. Try to add a 51st tenant under Acme; CloudCX refuses with 409 “Customer limit reached for this reseller”. That is the entitlement engine working as designed.
  3. Resolve (if needed). To let Acme grow, raise max_customers (Resellers → Entitlements). The very next create will succeed — no restart, no migration.
Hand-off checklist

After creating a tenant, give the customer: their username, the console URL (their reseller’s branded login domain if set, otherwise cloudcx.app), and the initial password over a secure channel, with a note to rotate it on first sign-in and to verify the contact email. Their next steps — agents, queues and channels — are covered in the tenant-facing guide.

Try it — provision a capped tenant

In a non-production / training environment:

Success looks like: you can articulate, from the live counters, exactly why the second create failed and what single change unblocked it.

6.6 Recap

CloudCX · Administrator & Training ManualCh. 06 · Tenants
CloudCXCloudCX Administrator & Training Manual
Ch. 07 · Users & Security
Chapter 07

Users, teams, skills and per-user security

Everyone who signs in to CloudCX — you, your resellers, every tenant administrator, every team lead and every front-line agent — is a typed user account. This chapter is the operator’s guide to that directory: how the account types fit the platform hierarchy, where each kind of user is created, how to give agents the skills that route work to them, and how to harden sign-in with a password policy and two-factor authentication. It ends with a full worked example — standing up a supervisor and two skilled agents from scratch — and a Try-it exercise.

The golden rule of who-creates-whom

CloudCX is a strict three-tier platform: Platform Admin → Reseller → Tenant, with team leads and agents beneath a tenant. Each tier creates the tier below it. As a platform administrator you create resellers and platform-direct tenants; a reseller creates its own customers and team; a tenant creates its own agents and supervisors. The platform Security view in your console is the read-only book of every account across all tiers, plus the controls that apply platform-wide (password policy) and to your own login (2FA).

7.1 The six account types

Every login is a row in the platform users table with one user_type. The type fixes what the account can see and do, and which slice of the tenant tree it is scoped to. There are exactly six types:

Table 7.1 — The user_type values, top to bottom of the hierarchy
TypeConsole labelScope (what they manage)Created by
adminPlatform adminEverything — all resellers, all tenants, platform settings. tenant_id and reseller_id are both NULL.Seeded / another admin
resellerResellerOne reseller’s brand, customers and team (reseller_id set, tenant_id NULL).Platform admin
tenantTenantThe primary administrator login for one tenant (tenant_id set).Reseller or platform admin
subtenantSub-tenantA delegated administrator within a tenant (department / business unit).Tenant admin
tlTeam leadA supervisor inside a tenant — sees and coaches a team of agents.Tenant / sub-tenant admin
agentAgentA front-line user who handles calls and digital conversations.Tenant / sub-tenant admin, or a reseller for its own team

Two phrases recur throughout this manual, so fix them now. A supervisor is a tl (team-lead) account — the words are interchangeable. An agent is an agent account. Skills-based routing (Chapter 8) only ever delivers interactions to agent and tl users, never to administrators.

Usernames are unique within a tenant

A username only has to be unique inside its tenant (the database key is (tenant_id, username)). So maria in Tenant A and maria in Tenant B are two different people — sign-in resolves the account by tenant. Platform admins have tenant_id = NULL, so keep admin usernames globally distinct to avoid ambiguity.

7.2 The platform account directory

Open the console at admin.cloudcx.app, sign in as a platform administrator, and choose Security in the dark sidebar under the Governance group. The top of this view is the Platform accounts roster — every login the platform knows about, fetched live from GET /api/v1/users and shown newest-first.

GovernanceSecurityPlatform accounts

admin.cloudcx.app
Operate
Dashboard
Reports
Tenancy
Resellers12
Tenants86
Governance
Security
Licensing
Settings
ALL SYSTEMS OPERATIONAL
v4.8 · ap-southeast
Security// 06 — trust & security
PRODAO
Governance

Platform accounts

Every account, fetched from the platform directory.

Refresh

Platform accounts

6 accounts
AccountRoleScopeStatusCreated
AOAdmin Operator[email protected]Platform adminPlatform active2026-01-04
ACAcme Comms[email protected]ResellerReseller active2026-03-22
NCNorthwind Care[email protected]TenantTenant active2026-05-08
MSMaria Santos[email protected]Team leadTenant active2026-05-09
JLJames Lee[email protected]AgentTenant active2026-05-09
PRPriya Raman[email protected]AgentTenant inactive2026-05-10

Accounts by type

derived from the live roster
AGENT — 2 / 6 · 33%
PLATFORM ADMIN — 1 / 6 · 17%
1
2
3
4
5
  1. Security in the Governance group — the active view (sidebar item data-view="security").
  2. Account column — display name (first + last, else username) with the email below it.
  3. Role — the friendly label for user_type (Platform admin / Reseller / Tenant / Sub-tenant / Team lead / Agent).
  4. ScopeTenant if tenant_id is set, Reseller if reseller_id is set, else Platform.
  5. Status pill — active, inactive or locked; Created is the UTC date.
The platform Security view, with the live account roster and the by-type rollup beneath it.
This roster is read-only by design

The platform Security view shows every account but does not create, edit or delete them from this screen — that keeps the platform console out of any single tenant’s day-to-day user administration. Accounts are provisioned where they belong: resellers and platform-direct tenants from Resellers / Tenants (Chapters 5–6), and a reseller’s own team and a tenant’s agents through the portals and API described next.

7.3 Creating agents and supervisors

Agents and supervisors are created one tier down from the platform — by a reseller for its own team, or by a tenant administrator for the contact-centre staff. The mechanics are the same everywhere: a username, an email, a starting password (which must satisfy the platform password policy — § 7.5), and a role. The role you pick becomes the new account’s user_type.

7.3.1 Roles a reseller may assign

A reseller manages its team from the reseller portal’s Team panel (/reseller → Team), backed by GET /api/v1/reseller/team and POST /api/v1/reseller/team. A reseller is deliberately restricted to two roles for its own staff:

A reseller can never mint a platform admin, a tenant, a sub-tenant or a team-lead login — the server forces the new account’s reseller_id to the caller’s own reseller and rejects any role outside {reseller, agent} with 422. Team leads (supervisors) are created inside a tenant, not in the reseller team panel; see § 7.3.3.

acme.cloudcx.app/reseller
Manage
Customers14
Plans
Team5
ALL SYSTEMS OPERATIONAL
brand · acme
Team// your reseller staff
PRODAC
Reseller portal

Team members

Users belonging to your reseller account.

+ Add team member
NameEmailRoleActive
ACAcme Comms[email protected] ResellerEdit
DODana Okafor[email protected] AgentEdit
RTRavi Tan[email protected] AgentEdit
1
2
3
  1. Add team member — opens the create form (the worked flow is in § 7.3.2).
  2. Role pill — the new member’s user_type, limited to Reseller or Agent.
  3. Active toggle — flips the account between active and inactive via PATCH /reseller/team/{id}.
The reseller’s Team panel — a reseller manages its own brand admins and agents here.

7.3.2 Procedure — add a team member (reseller)

  1. Open the Team panel. Sign in to the reseller portal and select Team. The list shows every user whose reseller_id is yours, newest-first.
  2. Click Add team member. A short form appears with four inputs: Username, Email, Password and Role.
  3. Enter the username. 3–128 characters, unique within your scope. This is what the person types to sign in.
  4. Enter the email. A valid address is required — it is used for display, password resets and (optionally) email sign-in codes.
  5. Set a starting password. It must satisfy the active password policy (§ 7.5). A violation returns 422 with a message listing exactly what is missing.
  6. Choose the role. Agent for a front-line user, or Reseller for another brand administrator. (Defaults to Agent.)
  7. Create. CloudCX hashes the password (Argon2), forces the new account onto your reseller, and adds the row. The account is active immediately. A duplicate username returns 409.

Add team member

×
Usernamedana.okafor
Password••••••••••••Must meet the platform password policy.
RoleAgent
CancelCreate member
The create-team-member form, mapping one-to-one onto POST /reseller/team.

7.3.3 Agents and supervisors inside a tenant

Within a tenant, the staff directory is richer: a tenant (or sub-tenant) administrator creates agents and team leads (supervisors) for the contact centre. The account types are the same six from Table 7.1 — you pick Agent for a handler and Team lead for a supervisor who will monitor, whisper and barge (see Chapter 11 on supervision). Programmatically, every account is created through the platform user API:

Create a supervisor (team lead) under a tenant
POST /api/v1/users
# body
{
  "username": "maria.santos",
  "password": "Wint3r!Garden42",
  "user_type": "tl",            # tl = team lead / supervisor
  "email": "[email protected]",
  "tenant_id": "<northwind-tenant-id>",
  "first_name": "Maria",
  "last_name": "Santos"
}

Swap "user_type": "tl" for "agent" to create a front-line agent instead. The new account inherits the tenant’s scope from tenant_id, so it can only ever see that tenant’s data. The starting password is validated against the platform policy exactly as in the portal flow.

Agent seats are licensed

The number of agent/team-lead seats a tenant may run is capped by its parent reseller’s licensed seat pool (the reseller’s max_agents entitlement). Creating staff consumes from that pool; when it is exhausted the create call is refused with 409. Watch the live counters on the Licensing view and in the reseller’s entitlement summary — the seat economics are covered in Chapters 5 and 6.

7.4 Skills — tag agents so the right work reaches them

A skill is a named competency — Billing, Spanish, Tier-2 Support, VIP — that you attach to agents at a proficiency level. Queues then require a skill at a minimum level, and the ACD only routes an interaction to an agent who holds every required skill at or above that level. This is skills-based routing; the routing engine itself is Chapter 8. Here we cover the three building blocks an administrator manages.

Skills bind agents to queues by competency
SkillBilling
Agent skillJames · level 3
+
Queue skillBilling q · min 2
Match3 ≥ 2 ✓ routed

The three building blocks, with their endpoints, are:

Skill

A name (and optional description) owned by a tenant. POST /api/v1/acd/skills.

Agent skill

A skill granted to a user at a proficiency level (1+). POST /acd/agents/{id}/skills.

Queue skill

A skill a queue requires at a min level. POST /acd/queues/{id}/skills.

7.4.1 Create a skill

Skills are managed under the ACD. Create one with a clear, reusable name — you will assign it to many agents and reference it from many queues:

Create the “Billing” skill
POST /api/v1/acd/skills
{ "name": "Billing", "description": "Invoices, refunds, payment disputes" }

# 201 Created
{ "id": "3f2a…", "tenant_id": "<tenant>", "name": "Billing", "description": "Invoices…" }

When a tenant or reseller administrator creates a skill, it is automatically stamped with their own tenant_id — you cannot create a skill in someone else’s tenant. A platform admin may target any tenant, or leave tenant_id NULL to create a shared platform-level skill.

7.4.2 Assign a skill to an agent (with a level)

Granting a skill to an agent records a proficiency level — a positive integer where higher means more capable. A common convention is 1 = trained, 3 = proficient, 5 = expert, but the scale is yours; what matters is consistency with the min level you put on queues.

northwind.admin.cloudcx.app
Contact centre
Queues7
Skills9
Agents42
ALL SYSTEMS OPERATIONAL
tenant · northwind
Skills// competency-based routing
PRODNC
ACD

Skill — Billing

Agents who hold this skill, and at what level.

Edit skill+ Assign agent

Agents with this skill

3 assigned
AgentTypeLevel
JLJames Lee Agent Level 3Remove
MSMaria Santos Team lead Level 5Remove
AKAnya Kraft Agent Level 1Remove
1
2
3
4
  1. Skills in the Contact-centre group — the catalogue of competencies for this tenant.
  2. Assign agent — grants the skill to a user (calls POST /acd/agents/{user_id}/skills with a level).
  3. Level — the agent’s proficiency; queues compare against this number.
  4. Remove — revokes the skill from that agent (DELETE /acd/agents/{user_id}/skills).
A skill’s detail, listing the agents who hold it and their proficiency levels.
  1. Open the skill. Under the ACD, choose Skills and open the skill you want to staff (for example Billing).
  2. Click Assign agent. Pick the agent or team lead from the tenant’s roster.
  3. Set the proficiency level. Enter an integer of 1 or more. Choose it deliberately — it is the value queues test against.
  4. Save. CloudCX records the (agent, skill, level) grant. Each agent may hold a skill only once, so re-adding the same skill returns 409 — remove and re-add to change the level.
Grant Billing at level 3 to an agent
POST /api/v1/acd/agents/<james-user-id>/skills
{ "skill_id": "3f2a…", "level": 3 }     # level defaults to 1 if omitted

7.4.3 Require a skill on a queue

Finally, tell a queue which skills it demands. A queue may require several skills, each with its own min level; an agent must clear every requirement to be eligible. Attach a required skill with:

Require Billing at min level 2 on the Billing queue
POST /api/v1/acd/queues/<billing-queue-id>/skills
{ "skill_id": "3f2a…", "min_level": 2 }    # min_level defaults to 1

With the example numbers, James (Billing 3) and Maria (Billing 5) clear the queue’s min 2, but Anya (Billing 1) does not — she will not be offered Billing interactions until her level is raised. You can confirm staffing live at GET /acd/queues/{id}/stats, which reports the queue’s member count, how many are available right now, and its required skills.

Membership vs. skill eligibility

Adding an agent as a queue member (POST /acd/queues/{id}/members) and granting the skills a queue requires are two different things. Routing needs both: an agent is offered a queue’s work only when they are a member and hold every required skill at the minimum level. Keep skills as the portable competency and membership as the assignment — an agent can hold “Spanish” without being staffed on every Spanish queue.

7.5 Password policy (platform-wide)

The password policy is a single platform-wide rule that every new password is checked against — reseller team members, tenant and reseller bootstrap logins, and any password change. You edit it from the platform console under Security → Password policy, backed by GET/PUT /api/v1/admin/platform/security/password-policy. The defaults are deliberately permissive (minimum length 8, no character-class requirements) so nothing breaks before you opt in.

admin.cloudcx.app
Governance
Security
Licensing
Settings
ALL SYSTEMS OPERATIONAL
v4.8 · ap-southeast
Security// password policy
PRODAO

Password policy

live · saved
Minimum length12

Require uppercase

At least one A–Z character.

Require lowercase

At least one a–z character.

Require digit

At least one 0–9 character.

Require symbol

At least one non-alphanumeric.

Save policy Reload
1
2
3
  1. Minimum length — the floor for every new password (bounded 1–128).
  2. Character-class toggles — require uppercase, lowercase, digit and/or symbol.
  3. Save policy — persists the singleton rule; Reload re-reads the stored value.
The Password policy panel — minimum length plus four optional character-class requirements.
  1. Open Security → Password policy. The panel loads the current rule (or permissive defaults if none has been saved).
  2. Set a minimum length. A practical floor is 10–14. The value is bounded 1–128 (a minimum above 128 would make every password impossible, since password fields cap at 128).
  3. Turn on the classes you want. Each toggle adds a requirement: at least one uppercase, lowercase, digit, or symbol (a symbol is any non-alphanumeric, including space).
  4. Save the policy. It takes effect immediately for the next password set anywhere on the platform.
The policy applies to NEW passwords only

Tightening the policy does not re-validate or expire existing passwords — current users keep their logins until they next change a password. When a new password fails, the create/change call returns 422 with a single human-readable message, for example: Password does not meet the policy: must contain at least 12 characters, an uppercase letter, a digit. If you need everyone moved to the stronger rule at once, drive a coordinated password reset.

7.6 Two-factor authentication and email sign-in codes

CloudCX supports two opt-in second factors, both off by default so that turning them on never disturbs anyone who has not enrolled. From Security → Two-factor authentication you manage the second factor on your own signed-in account; every account holder manages their own from their respective console.

7.6.1 Enrolling in TOTP two-factor

The panel walks through three states — enroll (scan a secret), activate (confirm a code, receive backup codes) and enabled. Behind them sit three endpoints: POST /auth/mfa/enroll, POST /auth/mfa/activate and POST /auth/mfa/disable.

admin.cloudcx.app

Two-factor authentication

setup in progress

Add this account to your authenticator app, then enter the 6-digit code it shows to finish.

Scan

otpauth QR / deep link — opens your authenticator app

Secret (manual entry)JBSW Y3DP EHPK 3PXP
6-digit code000000
Activate Cancel

Backup codes — save them now

shown once

Each code works once if you lose access to your authenticator. They will not be shown again.

7K4PM-Q2WNX
H9RDT-3VBKZ
XM2QJ-8PNRW
P4HZN-K7TMV
29WKD-RQHJ4
VT8MP-N3KXQ
TOTP enrollment: scan the secret, confirm a code to activate, then store the one-time backup codes.
  1. Click Enable 2FA. CloudCX generates a fresh secret and shows it as a QR / otpauth deep link, plus the raw secret and URI for manual entry. This does not turn 2FA on yet.
  2. Add it to your authenticator. Scan the code (or type the secret) into Google Authenticator, 1Password, Authy or similar. The app starts showing a rolling 6-digit code.
  3. Enter a current code and Activate. CloudCX verifies it against the pending secret. On success, 2FA is now on and you are shown 10 one-time backup codes in the format XXXXX-XXXXX.
  4. Save your backup codes. They are displayed once and never again — only their hashes are stored. Keep them somewhere safe; each lets you sign in once if you lose your authenticator.
  5. Confirm. Click “I’ve saved my codes.” The panel now reads Enabled ✓. Future sign-ins will ask for a code.
Backup codes are your only lockout escape

There is no admin “reset my 2FA” button — the secret is encrypted at rest and the platform never sees your codes. If you lose your authenticator and your backup codes, recovery means a manual, audited account intervention by the platform operator. Print or vault the backup codes the moment they appear, and store them apart from the device running the authenticator.

7.6.2 Signing in with 2FA, and turning it off

Once enabled, the sign-in flow asks for a second factor after the password is accepted. Supply a current TOTP code (or, in a pinch, an unused backup code) in the code field. If you omit it, the API replies 401 mfa_required so the console knows to prompt; a wrong code returns 401 Invalid 2FA code. Email sign-in codes work the same way, challenging with otp_required and delivering the code by email.

To turn 2FA off, open the panel (it shows Enabled ✓), enter a current 6-digit code, and click Disable 2FA. CloudCX verifies the code (a backup code also works), then clears the secret and all backup codes. The account returns to single-factor sign-in.

Lead by example

The single highest-value account to protect is yours — the platform administrator. Enrol your own login in 2FA first, then make it a stated requirement that every reseller and tenant administrator does the same. Combined with a strong password policy and (for sensitive deployments) IP access rules, this is the baseline you should hold every operator to.

7.7 Deactivating, locking and re-activating users

An account’s lifecycle is governed by its status, which has three values:

active
Normal — may sign in.
inactive
Deactivated — sign-in is refused with 403.
locked
Administratively locked — sign-in refused.

The most common operation — a leaver, or a contractor between shifts — is to deactivate rather than delete. Deactivating preserves all of the account’s history (CDRs, conversations, QA scores) while immediately barring sign-in. In a reseller’s Team panel this is the Active toggle (a one-click flip via PATCH /reseller/team/{id} with {"active": false}); for tenant staff the same status field is set to inactive.

  1. Find the account. Locate the user in the relevant Team / Agents list (or confirm it on the platform Security roster).
  2. Deactivate. Turn the Active toggle off (or set status = inactive). The change is immediate: any new sign-in is refused with 403 User is not active.
  3. Verify. The status pill flips to inactive on the roster. Existing tokens expire on their normal schedule; the account cannot obtain new ones.
  4. Re-activate when needed. Flip the toggle back on (status = active) to restore access — no re-creation, no lost history.
Deactivate, don’t delete — and mind the cascades

Prefer inactive for anyone who might return, or whose records you must retain. Hard deletion exists but cascades widely — deleting a tenant removes all its users, queues and skills via foreign-key cascade. A deactivated agent also vacates their licensed seat conceptually but keeps their skill grants, so re-activation restores them to exactly their previous routing eligibility.

7.8 Worked example — stand up a billing team

Northwind Care, a tenant under the Acme Comms reseller, is opening a billing line. As the administrator you will: create a supervisor and two agents, build the Billing skill, grade the agents, require the skill on the Billing queue, harden the password policy, and turn on 2FA for the supervisor. Follow it end-to-end.

  1. Tighten the password policy first. Under Security → Password policy, set Minimum length 12 and turn on uppercase, lowercase and digit. Save. Every account you create next must meet this bar.
  2. Create the supervisor (team lead). Provision maria.santos as a tl in the Northwind tenant with password Wint3r!Garden42 (12+ chars, mixed case, a digit — it passes).
  3. Create two agents. Provision james.lee and priya.raman as agent in the same tenant, each with a policy-compliant password.
  4. Create the Billing skill. POST /acd/skills with name Billing. Note its returned id.
  5. Grade the team. Grant Billing to Maria at level 5, James at level 3, and Priya at level 1 (still onboarding).
  6. Require the skill on the queue. On your Billing queue, add required skill Billing at min level 2.
  7. Staff the queue. Add Maria, James and Priya as queue members (POST /acd/queues/{id}/members).
  8. Protect the supervisor. Have Maria sign in and enrol her account in 2FA, saving her 10 backup codes.
  9. Verify routing eligibility. Call GET /acd/queues/{id}/stats. Maria and James clear min 2; Priya (level 1) does not — she is a member but not yet eligible for Billing work.
The skill calls, in order
# 4 — create the skill
POST /api/v1/acd/skills            { "name": "Billing" }            # -> id 3f2a…

# 5 — grade the three users
POST /api/v1/acd/agents/<maria>/skills   { "skill_id": "3f2a…", "level": 5 }
POST /api/v1/acd/agents/<james>/skills   { "skill_id": "3f2a…", "level": 3 }
POST /api/v1/acd/agents/<priya>/skills   { "skill_id": "3f2a…", "level": 1 }

# 6 — require it on the queue, min level 2
POST /api/v1/acd/queues/<billing-q>/skills  { "skill_id": "3f2a…", "min_level": 2 }

# 9 — confirm staffing
GET  /api/v1/acd/queues/<billing-q>/stats
# -> { members: 3, available_now: 2, required_skills: [{ skill_id: 3f2a…, min_level: 2 }] }

Result. The Billing queue is staffed by three members, two of whom are immediately eligible. When Priya finishes onboarding, remove and re-add her Billing grant at level 2 (or higher) and she joins the eligible pool with no other change. Maria’s sign-in is now protected by a second factor, and every account was forced to meet the stronger password policy at creation.

Try it — add a bilingual escalation path

Extend the worked example in your training tenant:

7.9 Recap

CloudCX · Administrator & Training ManualChapter 07 · Users & Security
CloudCXCloudCX Administrator & Training Manual
Ch. 08 · Numbers, Trunks & LCR
Chapter 08

Numbers, SIP trunks and carriers, and LCR

Every voice interaction on CloudCX crosses a carrier. Inbound, a customer dials one of your DIDs (the numbers you own) and the platform routes the call to a flow, queue, extension or mailbox. Outbound, the dialer hands the call to a SIP trunk — an outbound carrier — chosen by your least-cost-routing (LCR) rules. This chapter covers the whole Call Routing workspace: managing the inbound DID inventory, registering a SIP carrier, writing LCR rules that pick the cheapest matching trunk per destination prefix, and reading the review-only SBC config the platform generates for each trunk. It closes with a fully worked carrier cut-over and a hands-on Try-it exercise.

What CloudCX does — and does not — do to your SBC

CloudCX is the control plane. It stores your numbers, trunks and routing rules and uses them at call time to pick a gateway. It deliberately never writes to or reloads your live CloudCX Switch or CloudCX SBC configuration. Provisioning a trunk creates a row; the generated gateway config is review text for an operator to apply on the session border controller (SBC) by hand. This keeps a working voice path safe: until you both provision a trunk and install its gateway, nothing about outbound dialling changes.

8.1 Opening the Call Routing workspace

Open the console at admin.cloudcx.app, sign in with your platform-administrator account, and select Call Routing in the dark sidebar under the Platform group. The page brings four things together on one screen: the live ACD queues and inbound DIDs that decide where a call lands, and the SIP carrier trunks plus least-cost routing that carry outbound traffic.

Use the breadcrumb chip below as your map for the procedures in this chapter:

PlatformCall RoutingSIP carrier trunks

admin.cloudcx.app
Operate
Dashboard
Reports
Tenancy
Resellers
Tenants
Platform
Call Routing
Channels
Platform Credentials
Voice
IVR Builder
Campaigns
Call Routing// 04 — routing & inbound · live · /acd/queues + /dids
PRODAO
Platform

Call Routing

The live ACD queues and inbound DIDs, plus the SIP carrier trunks and least-cost routing that carry outbound traffic.

Refresh

ACD queues

4 queues
Salesprio 10 · max 90s
Supportprio 20 · max 120s

Inbound DIDs

3 numbers
+65 3158 1200 live
+44 20 4525 9000 live

SIP carrier trunks

2 trunks+ Add trunk
// 2 SIP carriers · least-cost routing below

Least-cost routing

3 rules+ Add rule
1
2
3
4
5
6
  1. Call Routing nav item, under the Platform group in the dark sidebar — the active workspace.
  2. Refresh re-pulls every panel from the API (queues, DIDs, trunks and LCR rules).
  3. ACD queues — read-only summary of the live queues an inbound call can land in (configured in Chapter 7).
  4. Inbound DIDs — read-only summary of the numbers you own and where each routes (managed under Voice › IVR Builder › Numbers).
  5. SIP carrier trunks panel with + Add trunk — your outbound carriers (§ 8.3).
  6. Least-cost routing panel with + Add rule — the prefix-to-trunk rules that pick a carrier (§ 8.4).
The Call Routing workspace: inbound (queues + DIDs) above, outbound (trunks + LCR) below.
Read the data source line

The grey monospace text beside the page title (// live · /api/v1/acd/queues + /dids) tells you the panels are showing real data. If a panel reads // data unavailable or a list shows // … unavailable, the call failed — check your session and the API before assuming the inventory is empty.

8.2 The inbound DID inventory

A DID (Direct Inward Dialing number) is a phone number a tenant owns. When a customer calls it, the inbound engine looks the dialled number up and routes the call to its configured destination. Every DID is tenant-scoped: a tenant user sees only their own numbers; a platform administrator sees all of them. Writes are admin-gated, so creating, editing or removing a DID requires an administrator.

On the Call Routing screen the Inbound DIDs panel is a read-only roster — it shows the number, its name, its destination and whether it is live. To add or edit a DID you use the dedicated Numbers view inside the IVR Builder, reached from Voice › IVR Builder › Numbers. Both surfaces talk to the same /dids API.

8.2.1 A DID’s four routing destinations

Each DID carries a destination_type and a destination_ref. The type chooses what kind of thing answers; the ref points at the specific one:

Table 8.1 — DID destination types and what the reference points at
destination_typeWhat answers the calldestination_ref is…
flowAn IVR flow (menus, prompts, business hours, branching).The published IVR flow’s id (picked from a list).
queueAn ACD queue — straight to skills-based agent routing.The queue’s reference.
extensionA single extension / endpoint.The extension number.
voicemailA mailbox that records a message.The mailbox identifier.
Disabled DIDs are kept, not deleted

Setting a DID inactive (active = false) stops it routing but preserves the row for history and easy re-enablement. Only delete a number you have genuinely released back to the carrier.

admin.cloudcx.app/ivr-builder.html
Voice
Flows
Numbers3
Numbers// /api/v1/dids
PRODAO
Voice

Numbers

The DIDs this tenant owns and where each inbound call is routed.

+ Add DID
NumberNameDestinationReferenceStatus
+65 3158 1200SG main line flow Main Greeting active Edit · Delete
+65 3158 1234SG sales DID queue sales active Edit · Delete
+44 20 4525 9000UK reception voicemail vm-uk-reception off Edit · Delete
1
2
3
4
5
  1. Numbers view in the IVR Builder — the live DID inventory (GET /dids).
  2. + Add DID opens the create modal (admin-gated POST /dids).
  3. Destination tag — the destination_type (flow / queue / extension / voicemail).
  4. Status active numbers route; off numbers are parked.
  5. Per-row Edit / Delete — both admin-only (PATCH / DELETE /dids/{id}).
The Numbers view: the DID inventory, each number routed to a flow, queue, extension or mailbox.

8.2.2 Adding a DID

  1. Open the Numbers view. Go to Voice › IVR Builder › Numbers and click + Add DID.
  2. Enter the number. Type the DID exactly as the carrier presents it inbound (E.164 is safest, e.g. +6531581200). This is the string the inbound engine matches a call against, so it must match what arrives on the trunk.
  3. Name it. Give a human label such as SG main line — this is what appears in the roster and in reports.
  4. Choose the destination type from flow, queue, extension or voicemail (Table 8.1).
  5. Set the reference. For flow and queue the modal lets you pick from a live list; for extension and voicemail you type the target. Only a published flow can serve calls, so publish first (Chapter 7).
  6. Leave Active on (the default) so the number routes immediately, then Save. A 201 adds the row; the platform stamps it onto the current tenant (a platform admin can target a specific tenant).
The DID is only half the path

Creating the DID tells CloudCX where to send a call to that number. It does not make the carrier deliver calls for that number to your SBC — that is provisioned with the carrier and routed in your inbound dialplan. A DID whose carrier delivery or destination isn’t ready will simply never ring.

8.3 SIP carrier trunks

A SIP trunk is an outbound carrier the platform can route a call over. Each trunk is one row holding the gateway address, optional SIP authentication, transport and codec preferences, and a channel cap. Critically, the trunk’s name doubles as the CloudCX Switch CloudCX Switch gateway name — the dialer routes through byondswitch/gateway/<name>/<number> — so the name is restricted to a safe gateway charset (letters, digits and . _ - only; no spaces or metacharacters).

8.3.1 Trunk scope: platform, reseller or tenant

Every trunk (and every LCR rule) carries two optional owners, which together set its scope:

Table 8.2 — Trunk / LCR-rule ownership scopes
tenant_idreseller_idScope — who routes over it
setA single tenant’s own trunk/rule.
setA reseller’s self-provisioned trunk/rule (shared by its tenants).
Platform-shared — a CloudCX wholesale trunk every scope can fall back to.

At call time a tenant call considers its own trunks plus the platform-shared ones; a reseller call considers its own plus platform-shared. A tenant can therefore never route over another tenant’s carrier, yet you can still offer a shared CloudCX wholesale trunk as a default. As a platform administrator you set the owner when you create the trunk; a self-provisioning reseller’s trunks are always pinned to that reseller.

8.3.2 The trunk form, field by field

From the Call Routing screen, click + Add trunk in the SIP carrier trunks panel. The form opens inline. Most fields are optional — only Name and Gateway host are required.

Namename
Required. The trunk’s name and its CloudCX Switch gateway name. Letters, digits, ., _, - only — e.g. primary-carrier or didww-sg. Rejected (422) if it contains a space or other metacharacter.
Gateway hostgateway_host
Required. The carrier’s SIP host or IP, e.g. sip.carrier.com. Used as the SIP realm and, unless a proxy is set, as the next hop.
Gateway portgateway_port
SIP signalling port, 1–65535. Defaults to 5060.
Max channelsmax_channels
Concurrent-call cap on this trunk. 0 means unlimited (shown as “no cap”).
Usernameusername
SIP auth username. Leave blank for an IP-authenticated trunk (the carrier permits your SBC by IP ACL, no digest auth).
Passwordpassword
Write-only secret. Accepted on create/update, stored encrypted at rest, and never returned by any read. On edit, leave blank to keep the current password; clearing it removes auth.
From domainfrom_domain
The SIP From domain, if the carrier requires a specific one. Defaults to the gateway host.
Proxyproxy
An outbound proxy to send signalling to, if different from the host. Defaults to host:port.
Transporttransport
UDP, TCP or TLS. Default udp. Use TLS for encrypted signalling where the carrier supports it.
Codecscodecs
An ordered, comma-separated preference list, e.g. OPUS,PCMU,PCMA. Leave blank to use the carrier/profile default.
Registerregister
Toggle. On = the gateway sends an outbound SIP REGISTER to the carrier (registration trunk). Off = IP-authenticated, no registration. Default off.
Enabledenabled
Toggle. Only enabled trunks are candidates for routing. Disable to take a carrier out of rotation without deleting it. Default on.
admin.cloudcx.app

New SIP trunk

Cancel ✕
Nameprimary-carrier
Gateway hostsip.carrier.com
Gateway port5060
Max channels // optional30
Username // optionalbyond-sg
Password // optional••••••••••••
From domain // optionaldefaults to host
Proxy // optionaloutbound proxy
TransportUDP
Codecs // comma-separatedOPUS,PCMU,PCMA
RegisterRegister with carrier (vs IP auth)
EnabledTrunk active for routing
CancelSave trunk
1
2
3
4
5
6
  1. Name — doubles as the CloudCX Switch gateway name; safe charset only. Required.
  2. Password — write-only; on edit the hint reads “leave blank to keep current”.
  3. Transport select — UDP / TCP / TLS.
  4. Register toggle off — this trunk is IP-authenticated.
  5. Enabled toggle on — the trunk is a routing candidate.
  6. Save trunkPOST /admin/telephony/trunks (the password is encrypted at rest).
The Add-trunk form, configured for an IP-authenticated primary carrier.

8.3.3 Procedure — register a carrier

  1. Open the form. On Call Routing, click + Add trunk.
  2. Name the trunk with a safe, descriptive gateway name — e.g. primary-carrier. Remember this becomes the gateway your dial strings reference.
  3. Enter the gateway host (and port if not 5060) from your carrier’s SIP details.
  4. Choose the authentication model. For an IP-authenticated trunk leave Username/Password blank and Register off. For a registration trunk, enter Username + Password and turn Register on.
  5. Set transport and codecs to match the carrier (e.g. udp + OPUS,PCMU,PCMA), and set Max channels to your contracted concurrency (or leave 0 for unlimited).
  6. Confirm Enabled is on and click Save trunk. A 201 adds the row to the trunk list and to the LCR Trunk dropdown. The carrier is not yet carrying calls — you still need an LCR rule (§ 8.4) and the SBC gateway installed (§ 8.5).

A saved trunk renders as one row: a green dot when enabled (grey when disabled), the name, and a monospace meta line of host:port · TRANSPORT · register|IP auth, followed by its channel cap (“30 ch” or “no cap”), an enabled / disabled pill, and three row actions: view config, edit and delete.

admin.cloudcx.app

SIP carrier trunks

2 trunks+ Add trunk
primary-carriersip.carrier.com:5060 · UDP · IP auth
30 ch enabled ⟨⟩ ✎ 🗑
backup-carriersip2.carrier.net:5061 · TLS · register
no cap disabled ⟨⟩ ✎ 🗑
1
2
3
4
5
  1. Status dot — green = enabled, grey = disabled; with the name and the host:port · transport · auth meta.
  2. Channel cap — “30 ch” or “no cap” for unlimited (max_channels = 0).
  3. Enabled pill — whether the trunk is a routing candidate.
  4. Row actions, left to right: view config (⟨⟩), edit (✎) and delete (🗑).
  5. A disabled trunk stays listed but is skipped by routing — here a TLS registration backup, parked.
The trunk list: an enabled IP-auth primary and a disabled TLS registration backup.
Editing a trunk never reveals its password

Click edit (✎) and the form repopulates every field except the password, which shows the hint leave blank to keep current. Save with the password blank to keep the stored secret; type a new value only when you are rotating it. To remove authentication entirely (move to IP auth), clear the password field explicitly.

8.4 Least-cost routing (LCR)

With trunks registered, least-cost routing decides which trunk carries each outbound call. An LCR rule maps a destination prefix (the leading digits of the dialled number) to a trunk, with a priority and an optional per-minute cost. At call time the platform evaluates the rules in scope and picks the best matching trunk.

Idle-safe by design

With no trunks and no LCR rules, outbound dialling is byte-for-byte unchanged: the platform finds no match and falls back to the existing default gateway (fs_outbound_gateway). Selection is also fully defensive — any lookup error degrades to the fallback — so turning LCR on can never take down a working dial path. You opt in to LCR by adding rules.

8.4.1 How a trunk is chosen

The dialled number is normalised to digits, then candidate rules (those in the call’s scope whose trunk is enabled) are ranked. Selection follows this order:

  1. Prefix match. Keep only rules whose destination_prefix is a leading prefix of the dialled digits. The empty prefix “” is a catch-all that matches everything.
  2. Longest prefix first. The most specific prefix wins — 6597 beats 65 beats the catch-all for a Singapore mobile.
  3. Most-specific scope. Among equal-length prefixes, the call’s own tenant/reseller rule beats a platform-shared one.
  4. Lowest priority value. Then the lowest priority number wins (lower = preferred).
  5. Lowest cost, then newest. Then the lowest per_minute_cost (rules without a cost rank after those with one), and finally the most recently created rule.

The winning rule’s trunk supplies the CloudCX Switch gateway name for the byondswitch/gateway/<name>/<number> dial string. If that trunk is missing or disabled, or its name fails the safe-charset check, selection yields nothing and the call falls back to the default gateway — never a broken dial string.

LCR selection — most-specific, cheapest enabled trunk wins
Dial+65 9123 4567
Normalise6591234567
Match prefixes"" · 65 · 6591
Pick trunklongest → scope → prio → cost
Dial outbyondswitch/gateway/<name>

8.4.2 The LCR rule form

In the Least-cost routing panel, click + Add rule.

Destination prefixdestination_prefix
The leading digits to match, e.g. 1, 44, 65. Non-digits are stripped (a leading + or spaces are fine to type). An empty value is the catch-all default. Required by the UI.
Trunktrunk_id
Required. Pick from your registered trunks. A rule must reference an existing trunk (422 otherwise).
Prioritypriority
Lower wins among equal-length prefixes. Use this to order primary vs. backup carriers for the same prefix.
Per-minute costper_minute_cost
Optional wholesale rate (to 4 decimals). Used as a tie-breaker and for reporting; rules with a cost are preferred over those without when everything else ties.
admin.cloudcx.app

New LCR rule

Cancel ✕
Destination prefix65
Trunkprimary-carrier
Priority // lower wins10
Per-minute cost // optional0.0080
CancelAdd rule

Least-cost routing

3 rules
6591prefix → routed via trunk budget-mobileprio 5$0.0040/min🗑
65prefix → routed via trunk primary-carrierprio 10$0.0080/min🗑
catch-all → routed via trunk primary-carrierprio 100🗑
1
2
3
4
5
  1. Destination prefix — digits only; empty = catch-all.
  2. Trunk select — populated live from your registered trunks.
  3. Priority — lower wins among equal-length prefixes.
  4. Most-specific rule (6591) sits at the top — it wins for Singapore mobiles.
  5. The catch-all (shown as ) is the lowest-priority fallback for everything else.
Adding an LCR rule, with the resulting rule list ordered most-specific first.
Mind the catch-all

A catch-all rule (empty prefix) routes every destination that no longer prefix matches — including international numbers you may not have rated. Give it a sensible high priority number (so any specific rule beats it) and point it at a trunk that can legitimately reach everywhere, or omit it and let unmatched calls fall back to the default gateway.

8.5 The review-only generated config

Provisioning a trunk records its details; it does not touch your SBC. To install the gateway you ask CloudCX to generate the config text for the trunk, review it, and apply it on the SBC yourself. Click the view config action (⟨⟩) on a trunk row to open the config viewer.

The viewer calls GET /admin/telephony/trunks/{id}/config and shows two blocks for the same trunk:

Because the operator needs a working gateway, the real SIP password is embedded in this text (decrypted only here) — which is exactly why the endpoint is admin-gated. If the password cannot be decrypted (no key, or none stored) the config emits the placeholder <SET_PASSWORD_ON_SBC> so the text is obviously incomplete rather than silently wrong.

Generated config — primary-carrier

Review and apply this on the SBC — it is not auto-applied.
# ============================================================
# Generated SBC gateway config for trunk 'primary-carrier'
#   host=sip.carrier.com:5060 transport=udp
#   REVIEW ONLY — apply manually on the SBC.
# ============================================================

<!-- CloudCX Switch CloudCX Switch gateway for SIP trunk primary-carrier -->
<gateway name="primary-carrier">
  <param name="username" value="byond-sg"/>
  <param name="password" value="s3cr3t-from-vault"/>
  <param name="auth-username" value="byond-sg"/>
  <param name="realm" value="sip.carrier.com"/>
  <param name="proxy" value="sip.carrier.com:5060"/>
  <param name="register" value="false"/>
  <param name="transport" value="udp"/>
  <param name="codec-prefs" value="OPUS,PCMU,PCMA"/>
</gateway>

# CloudCX SBC edge SBC config for SIP trunk primary-carrier
# dispatcher.list (reload: kamcmd dispatcher.reload)
# 1 sip:sip.carrier.com:5060;transport=udp 0 0 name=primary-carrier
CloseCopy config
Figure 8.7 — The review-only config viewer: CloudCX Switch gateway block plus CloudCX SBC snippet, with a Copy button.
The generated config carries a live secret

The copied text contains the trunk’s real SIP password. Treat it like any credential: copy it straight into your SBC change, don’t paste it into chat or tickets, and clear your clipboard afterwards. Nothing about generating this text changes the live SBC — you must apply and reload it deliberately.

8.6 Worked example — cut Singapore mobile traffic to a cheaper carrier

Goal: route Singapore mobile calls (prefix 6591…6599) over a cheaper wholesale carrier, while everything else continues to use the primary carrier — with the primary as the safety net.

Existing trunk
primary-carrier · enabled
New trunk
budget-mobile · $0.0040/min
Mobile prefix
659 (SG mobile)
Everything else
stays on primary-carrier
  1. Register the new carrier. On Call Routing+ Add trunk, create budget-mobile with the wholesaler’s host, the right transport/codecs, your channel cap, and Enabled on. Save and confirm the 201.
  2. Install its gateway. Click view config on the budget-mobile row, review the CloudCX Switch block, apply it on the SBC and run CloudCX Switch service external rescan. Verify the gateway shows up / registers as expected.
  3. Add the specific LCR rule. In Least-cost routing+ Add rule: prefix 659, trunk budget-mobile, priority 5, cost 0.0040. Save.
  4. Add (or confirm) the catch-all. Ensure a rule with an empty prefixprimary-carrier at a high priority number (e.g. 100) exists, so all non-mobile traffic still has a home.
  5. Confirm ordering. The list should show 659 above the catch-all (longest prefix first). A test dial to +65 9123 4567 now matches 659 and routes via budget-mobile; a dial to a +65 6… landline matches only the catch-all and stays on primary-carrier.
  6. Validate, then watch costs. Place a live test call to a mobile and confirm it connects over the new carrier (CDR / SBC logs). Keep the catch-all on the primary so any routing gap fails safe to a known-good trunk.

Why this is safe: the new carrier only takes traffic the moment its LCR rule exists and its gateway is installed. If you add the rule but skip the gateway, the dialer can’t reach the trunk and the call falls back to the default gateway — you never strand mobile calls on a half-built carrier.

Try it — build and prove an LCR ladder

In a non-production / training environment:

Success looks like: you can predict, for any dialled number, which trunk LCR will pick (or that it falls back), and you can explain why deleting all rows is non-disruptive.

8.7 Recap

CloudCX · Administrator & Training ManualCh. 08 · Numbers, Trunks & LCR
CloudCXCloudCX Administrator & Training Manual
Ch. 09 · Call-Flow Builder
Chapter 09

The visual IVR / call-flow builder

When a caller dials one of your numbers, something has to decide what they hear and where they land — a greeting, a press-1-for-sales menu, a queue of agents, a voicemail box, or a straight transfer to an extension. In CloudCX that decision is a flow: a small graph of nodes you draw on a drag-and-drop canvas, wire together, validate, and publish. This chapter takes you through the builder end to end — the canvas and its parts, every node type and its fields, how a flow is serialized and validated, how to attach it to a phone number, and a fully worked main-menu · hours · queue IVR you can reproduce in minutes.

What a flow actually is

A published flow is stored as a single JSON document of the shape { "start": "<node id>", "nodes": { "<id>": {…}, … } }. The builder is just a friendly editor for that document — it emits the same JSON the inbound voice engine reads at call time. The two halves share one contract (app/schemas/ivr.py), so anything the builder lets you save is something the interpreter knows how to walk. The flow does not run until you press Publish and a DID points at it.

9.1 Opening the builder

The call-flow builder is a dedicated tool that lives beside the admin console at admin.cloudcx.app/ivr-builder.html. It uses the same sign-in as the console (your admin session carries over); if your session has lapsed it shows a brief Sign-in required gate and bounces you to admin.cloudcx.app to re-authenticate. From the console you reach it via the Voice group in the sidebar; from inside the builder the ← CONSOLE chip in the top-left returns you to the dashboard.

Two things to know before you start drawing:

admin.cloudcx.app/ivr-builder.html
Palette
Play
Menu
Queue
Dial
Hours
Voicemail
Hangup
DRAG ONTO CANVAS
ENV · PROD
Main line IVR// visual IVR · inbound voice routing
Builder DIDs PRODAO
Tenant Acme Retail Main line IVR draft
New Load ▾ Save Publish

Design your inbound flow

Drag a node from the palette onto this canvas, wire the outputs to targets, then Save and Publish. Or hit Load to open an existing flow.

100% + FIT
1
2
3
4
5
6
7
  1. Node palette — the seven node types, each in its own accent colour. Drag one onto the canvas (or double-click to drop it near the top-left).
  2. Builder / DIDs mode tabs — switch between the flow canvas and the inbound-number table (§ 9.7). Builder is selected here.
  3. Environment chip — confirms you are editing in PROD; the avatar shows the signed-in admin.
  4. Tenant, flow name & status — the owning tenant (platform-admin picker), an editable flow name, and a status pill: new / unsaved / draft / published.
  5. Toolbar actionsNew, Load ▾ (open an existing flow), Save, and the gradient Publish.
  6. Canvas — the dotted-grid workspace where nodes live and edges are drawn. Empty until you add a node.
  7. Zoom controls — zoom out / level / zoom in, and FIT to reset the view to 100 % and scroll home.
The call-flow builder on first open — an empty canvas, the node palette and the toolbar.
Tip · Pick the tenant first

If you are a platform administrator, set the Tenant selector before you save a new flow — the owning tenant is stamped at create time and cannot be changed afterwards. A tenant user has no picker; flows are pinned to their own tenant automatically.

CloudCX · Administrator & Training ManualCh. 09 · Call-Flow Builder
CloudCXCloudCX Administrator & Training Manual
Ch. 09 · Call-Flow Builder

9.2 The canvas, nodes and edges

A flow is built from three moving parts: nodes (the boxes), edges (the arrows that connect them), and a single Start node (the entry point the call begins at). You arrange nodes on the canvas purely for your own readability — position is a convenience, not part of the saved flow — while the edges are the routing logic.

Every node card has the same anatomy. Knowing it makes wiring fast:

★ Start
M
Menu
main_menu
“Press 1 for sales, 2 for support” · 2 options
Press 1sales_q
Press 2support_q
Invalid input
No input (timeout)closed_vm
Q
Queue
sales_q
→ sales · 120s
On timeoutclosed_vm
1
2
3
4
5
6
  1. Start flag — the pink “★ Start” ribbon marks the entry node. Exactly one node carries it. Double-click any node to move Start to it.
  2. In-port — the dot on the top edge. Edges arrive here; you drop an outgoing connection onto it to link into the node.
  3. Header — the type icon (colour-coded), the type label, and the node’s id (its key in the JSON). Drag the header to reposition the card.
  4. Output port label — one row per outgoing edge. A menu shows one row per digit (Press 1, Press 2) plus the Invalid input and No input (timeout) fallbacks.
  5. Output knob — the small ringed dot at the right of each port. Drag it to a target node’s in-port to connect; a filled knob means the port is linked, hollow means unset.
  6. Summary line — a one-glance recap (prompt text, target queue + timeout, mailbox, …) so you can read a card without opening the inspector.
Node anatomy — a selected menu node (Start) wired to a queue node.

9.2.1 Working the canvas

  1. Add a node. Drag a type from the palette onto the canvas, or double-click a palette item to drop it near the top-left. The first node you add automatically becomes Start.
  2. Move a node. Drag its header. Positions are visual only and are not persisted — on reload the builder auto-lays-out the graph top-down from Start.
  3. Connect two nodes. Press on an output knob and drag to the target node’s top in-port; release to link. The target’s in-port highlights while you hover a valid drop.
  4. Select & edit. Click a node to select it (its edges turn pink) and edit its properties in the right-hand inspector.
  5. Set Start. Double-click a node, or select it and use Set as Start in the inspector footer.
  6. Delete a node. Select it and press Delete (or Delete node in the footer). Any edges pointing at it are cleared automatically.
A node can’t connect to itself

Dropping a connection back onto its own source node is refused with a toast. Loops between nodes are allowed (a menu’s Invalid input commonly points back at the menu to re-prompt), and the interpreter has a per-call visit cap so a misdrawn cycle can never wedge a live call.

CloudCX · Administrator & Training ManualCh. 09 · Call-Flow Builder
CloudCXCloudCX Administrator & Training Manual
Ch. 09 · Call-Flow Builder

9.3 The seven node types

Every node has a type and a small set of fields. The table below is the complete catalogue — the same set the palette offers and the same set the interpreter executes. The Outputs column lists each node’s outgoing edges; an output that is left blank simply means “end the call here.”

Table 9.1 — Node types, their key fields and outgoing edges
NodeWhat it doesKey fieldsOutputs (edges)
PlayPlays a prompt, then continues.promptnext required
MenuPlays a prompt, gathers DTMF digit(s), routes on the pressed key.prompt, timeout_ms (5000), max_digits (1), optionsone per digit (options), invalid, timeout
QueueEnqueues the caller into an ACD queue; plays hold music while hunting an agent.queue_ref, timeout_sec (120)on_timeout
DialBridges the caller to an extension / number.target, timeout_sec (30)on_no_answer
HoursBranches on business hours evaluated in a timezone.timezone, ranges[]open req, closed req
VoicemailPlays an optional greeting, records a message to a mailbox.mailbox, greeting (optional)next (optional)
HangupTerminates the call.none

9.3.1 Prompts — what the caller hears

Three node types (Play, Menu, and the optional Voicemail greeting) carry a prompt. A prompt has a kind and one payload field:

TTS kind:"tts"
Synthesizes the text you type to speech at call time. The everyday choice.
Say kind:"say"
Speaks text via the say engine (useful for numbers/IDs read out literally). Also requires text.
Audio kind:"audio"
Plays a pre-recorded file from a url (e.g. an https://…/greeting.wav or an uploaded asset). Requires url.

The inspector swaps the field automatically: pick Audio and you get a URL box; pick TTS or Say and you get a Text box. A prompt that is missing its text (or url) is flagged before you can publish.

9.3.2 How a flow runs at call time

Understanding the runtime makes you a better flow author. When a call arrives the engine resolves the dialled DID, finds its destination, and — for a flow destination — loads the published, active flow, validates it, answers, then walks the graph from start:

Inbound call
Resolve DID
Load & validate flow
Answer
Walk from start
Inbound pipeline for a flow-type DID (the interpreter in flow_runtime.py).
The flow must be published and a DID must point at it

The interpreter only loads flows that are both published and active. A saved-but-unpublished draft never answers a call, and even a published flow does nothing until a DID routes to it (§ 9.7). Saving and publishing are two distinct steps.

CloudCX · Administrator & Training ManualCh. 09 · Call-Flow Builder
CloudCXCloudCX Administrator & Training Manual
Ch. 09 · Call-Flow Builder

9.4 The properties inspector

Selecting a node opens its inspector on the right. The panel is type-aware: it always shows the node’s id (renameable) at the top, then the fields and routing for that type, with a footer to set Start or delete the node. Every change is live — the canvas card and edges update as you type, and the flow is marked unsaved.

M
Menu node
// main_menu · START
● Node
Node idmain_menuUnique key in the flow JSON. Renaming updates all links automatically.
● Prompt
Prompt kindTTS (synthesize text)
TextPress 1 for sales, 2 for support.
● Menu options (digit → node)
1sales_q (queue)
2support_q (queue)
+ Add option
Set as Start Delete node
1
2
3
4
5
  1. Inspector head — the node type and its id; a START tag appears when this is the entry node.
  2. Node id — rename the node’s key here. Renaming repoints every edge that targeted it, automatically and atomically.
  3. Prompt editor — choose TTS / Say / Audio; the field below switches between a Text box and a URL box accordingly.
  4. Menu options — one digit → target row each. + Add option grabs the next free key (1–9, then 0, #, *); the ✕ on a row removes it.
  5. FooterSet as Start promotes this node to the entry point; Delete node removes it and clears inbound edges.
The inspector for a menu node — id, prompt, digit options and footer actions.

9.4.1 Per-type fields at a glance

Queue

Pick the ACD queue from the live list (the names come from /api/v1/acd/queues); set a timeout in seconds; wire On timeout to a fallback (often a voicemail).

Dial

Type a target extension or number (e.g. 1001 or +6531590000), a ring timeout, and an On no answer branch.

Hours

Set an IANA timezone (e.g. Asia/Singapore), add one or more open windows (day + HH:MM–HH:MM), then wire When OPEN and When CLOSED.

Voicemail

Give a mailbox name (e.g. acme-main); optionally enable a greeting prompt; optionally set After to continue, else the call ends after recording.

Tip · Rename nodes to read like a story

The default ids are menu_1, queue_1, … They work, but rename them to intent — main_menu, sales_q, closed_vm — and your flow JSON (and any later audit) reads like plain English. Renames repoint links for you, so do it early and often.

CloudCX · Administrator & Training ManualCh. 09 · Call-Flow Builder
CloudCXCloudCX Administrator & Training Manual
Ch. 09 · Call-Flow Builder

9.5 Saving, validating and publishing

Authoring a flow has three gears: Save (persist a draft), validate (the builder’s pre-flight check, mirrored by the server), and Publish (make it live). A draft can be saved at any time, even half-finished; publishing is gated on a clean validation.

9.5.1 Save

  1. Name the flow. Type a name in the toolbar (a name is required to save). For a platform admin, confirm the Tenant is correct first.
  2. Press Save. A new flow is created (POST /api/v1/ivr, status 201); an existing one is updated (PATCH /api/v1/ivr/{id}). The status pill becomes draft and the toolbar shows the version and short id.
  3. Keep saving. Any edit flips the status to • unsaved; the builder also warns if you try to leave the page with unsaved work.

9.5.2 What validation checks

Both the builder (instantly, in-browser) and the server (authoritatively) run the same rules — the contract in app/schemas/ivr.py. A flow is publishable only when all of the following hold:

2 issues to fix before publishing:
• main_menu: Menu has no digit options.
• greeting → “welcom” (target does not exist).
Flow published — it is now live for inbound calls.
The validation banner — specific, per-node errors (top) and the published confirmation (bottom).

9.5.3 Publish

  1. Press Publish. The builder runs validation first. If there are problems they appear in the banner with a count and a per-node list — fix them and try again.
  2. Auto-save if needed. If the flow is unsaved (or never saved), publishing saves it first, then publishes — you do not have to press Save separately.
  3. Server re-validates. Publishing calls POST /api/v1/ivr/{id}/publish, which re-validates the stored definition. A bad graph returns 422 with the detail surfaced inline (so the UI and the engine can never disagree).
  4. Confirm live. On success the status pill turns published and the green banner reads “Flow published — it is now live for inbound calls.”
Editing a published flow

You can keep editing after publishing. Your edits are saved as the working draft; re-press Publish to push the new version live. Because a DID resolves the published flow by id (or name), re-publishing the same flow updates callers’ experience without touching the DID.

CloudCX · Administrator & Training ManualCh. 09 · Call-Flow Builder
CloudCXCloudCX Administrator & Training Manual
Ch. 09 · Call-Flow Builder

9.6 Worked example — the main-line IVR

Let’s build a complete, real-world inbound flow for Acme Retail: during business hours, greet the caller and offer a menu (sales or support, each into its own queue); outside hours — or whenever a menu times out or no agent answers — take a voicemail. This is the canonical pattern and it exercises six of the seven node types.

The target graph has six nodes:

hours
open→
greeting (play)
main_menu
main_menu
1→
sales_q (queue)
2→
support_q (queue)
hours closed · menu timeout · queue on_timeout
closed_vm (voicemail)
The main-line IVR: an hours gate, a greeting, a two-option menu, two queues and a shared voicemail fallback.

9.6.1 Build it step by step

  1. New flow & name. Press New, set the Tenant to Acme Retail, and name it Main line IVR.
  2. Drop the Hours gate. Drag a Hours node on. As the first node it becomes Start. In the inspector set Timezone Asia/Singapore and add windows for Mon–Fri 09:00–18:00 (one window per weekday). Rename its id to hours.
  3. Add the greeting. Drag a Play node, rename it greeting, set a TTS prompt: “Welcome to Acme Retail.”
  4. Add the menu. Drag a Menu node, rename it main_menu, set a TTS prompt: “Press 1 for sales, 2 for support.” Leave Timeout 5000 ms and Max digits 1.
  5. Add the two queues. Drag two Queue nodes; rename them sales_q and support_q; pick the matching ACD queues; leave the 120 s timeout.
  6. Add the voicemail. Drag a Voicemail node, rename it closed_vm, set Mailbox acme-main, and enable a greeting: “We are closed. Please leave a message.”
  7. Wire the open path. Connect hoursWhen OPENgreeting; greetingNextmain_menu.
  8. Wire the menu. Connect main_menu Press 1sales_q and Press 2support_q. Connect its No input (timeout)closed_vm; point Invalid input back at main_menu to re-prompt.
  9. Wire the fallbacks. Connect hours When CLOSEDclosed_vm; and each queue’s On timeoutclosed_vm.
  10. Save, then Publish. Press Save (expect draft), then Publish. The green banner confirms it is live.
The flow this produces — exactly the JSON the builder saves and the engine walks.
{
  "start": "hours",
  "nodes": {
    "hours": { "type":"hours", "timezone":"Asia/Singapore",
      "ranges":[{"day":0,"open":"09:00","close":"18:00"}, /* …Tue–Fri… */],
      "open":"greeting", "closed":"closed_vm" },
    "greeting": { "type":"play",
      "prompt":{"kind":"tts","text":"Welcome to Acme Retail."}, "next":"main_menu" },
    "main_menu": { "type":"menu", "timeout_ms":5000, "max_digits":1,
      "prompt":{"kind":"tts","text":"Press 1 for sales, 2 for support."},
      "options":{"1":"sales_q","2":"support_q"},
      "invalid":"main_menu", "timeout":"closed_vm" },
    "sales_q":   { "type":"queue", "queue_ref":"sales",   "timeout_sec":120, "on_timeout":"closed_vm" },
    "support_q": { "type":"queue", "queue_ref":"support", "timeout_sec":120, "on_timeout":"closed_vm" },
    "closed_vm": { "type":"voicemail", "mailbox":"acme-main",
      "greeting":{"kind":"tts","text":"We are closed. Please leave a message."} }
  }
}
Tip · Trace it before you publish

Read the graph as a caller would: Mon 10:30 → hours open → greeting → menu → press 1 → sales queue (120 s) → if no agent → voicemail. Sat 14:00 → hours closed → straight to voicemail. If every path ends at an agent or a voicemail, you have no dead ends.

CloudCX · Administrator & Training ManualCh. 09 · Call-Flow Builder
CloudCXCloudCX Administrator & Training Manual
Ch. 09 · Call-Flow Builder

9.7 Attaching a number — the DIDs tab

A flow only answers calls once a DID (a phone number you own) points at it. Switch to the DIDs tab to see every inbound number and where each one routes. A DID’s destination is one of four kinds: a flow (IVR), a queue (straight to ACD, no menu), an extension, or a voicemail mailbox.

admin.cloudcx.app/ivr-builder.html
DID Management// inbound numbers
BuilderDIDs PRODAO
// inbound numbers

DID management

Phone numbers you own and where each inbound call goes.

+ Add DID

Numbers

// /api/v1/dids
NumberNameDestinationRoutes toState
+6531591234Main line — Sales flowMain line IVR active
+6531590010Support hotline queuesupport active
+6531590099After-hours voicemailacme-main off
1
2
3
4
  1. DIDs mode — the inbound-number table, loaded live from /api/v1/dids. Switch back to Builder any time.
  2. Add DID — opens the number-routing modal (below).
  3. Destination typeflow / queue / extension / voicemail, colour-coded.
  4. State — only active DIDs route calls; an inactive number is parked.
The DIDs tab — inbound numbers and their routing destinations.

9.7.1 Point a number at your flow

  1. Add DID. Click + Add DID. Enter the Phone number (e.g. +6531591234) and a Label (e.g. Main line — Sales).
  2. Choose destination. Set Destination type to Flow (IVR), then pick Main line IVR from the Target flow list. (Picking Queue instead lists ACD queues; Extension/Voicemail show a text box.)
  3. Activate & save. Leave Active on, press Save DID (POST /api/v1/dids). The number now routes inbound calls into your published flow.
Draft flows can be selected but won’t answer

The Target flow picker lists drafts too (marked (draft)). You can attach a DID to a draft, but the engine only runs published flows — until you publish, callers to that number hear the “menu is unavailable” fallback. Publish the flow, then your number is fully live.

Try it · Build, publish and route a flow

In a non-production tenant, reproduce the worked example and prove every branch end to end:

  1. Build the six-node Main line IVR from § 9.6. Rename every node to intent.
  2. Press Publish with the menu’s options deliberately empty — confirm the banner blocks you with main_menu: Menu has no digit options. Add the options and re-publish to green.
  3. Delete the closed_vm node, then re-publish — observe the dangling-reference errors naming each edge that pointed at it (e.g. main_menu → closed_vm). Re-add it and re-wire.
  4. Switch to DIDs, add a test number, and route it to Main line IVR. Confirm the row shows destination flow, active.
  5. Stretch: set the Hours window to a closed period (e.g. yesterday’s hours) and reason about which branch a call takes right now; then place a test call and confirm you land in voicemail.

Success looks like: a published flow whose every path ends at an agent or a voicemail, a number routed to it, and a clear mental trace of the open and closed paths.

9.8 Recap

Note · Where to go next

Queues your menu feeds are configured in the ACD chapter; the agents who answer them, and the voicemail boxes your flows record into, are covered alongside the voice channel. With flows and DIDs in place, your inbound voice routing is complete end to end.

CloudCX · Administrator & Training ManualCh. 09 · Call-Flow Builder
CloudCXCloudCX Administrator & Training Manual
Ch. 10 · ACD
Chapter 10

ACD — queues, skills and routing strategies

The Automatic Contact Distributor (ACD) is the brain that decides which agent handles which interaction. When a call rings in or a web chat opens, CloudCX picks the right available agent from a queue, using the agents’ skills and the queue’s chosen routing strategy. This chapter shows you how queues, skills, queue membership and the four strategies fit together, how to read a queue’s live stats, and — with a fully worked skills-based example — how to make sure the right interaction always reaches the right person.

Where you do this work

You can see the live ACD queues in the platform console under VoiceCall Routing at admin.cloudcx.app. The full set of ACD write operations — creating queues and skills, adding members, attaching required skills and granting agent skills — is exposed by the ACD API under /api/v1/acd. Throughout this chapter the read views are shown as console mockups and the management actions as the matching API calls, so you can drive them from a provisioning script or your own admin tooling.

10.1How the ACD thinks

CloudCX’s ACD joins three sources of truth to choose the next agent. Two are static configuration you manage (membership and skills, held in the database); the third is live presence — who is signed in and available right now — held in a fast in-memory store and updated every time an agent changes state. The chosen agent is always the intersection of all three, narrowed by the queue’s strategy.

The five ACD building blocks and how they relate
QueueA waiting line for one channel, with a distribution strategy. Interactions arrive here; agents are members of it; it may require skills.queues
SkillA competency tag — billing, spanish, tier-2. Agents hold skills; queues require them.skills
Agent skillAn agent holds a skill at a proficiency level (1 = basic, higher = stronger).agent_skills
Queue skillA skill the queue requires at a min level — an agent must meet it to be eligible.queue_skills
Queue memberAn agent staffed on (assigned to) the queue — the candidate pool before skills and presence are applied.queue_members

Put plainly, the ACD answers one question on every inbound interaction: “Of the agents staffed on this queue, who is available right now AND meets every skill the queue requires — and among those, who should get it?” The first two clauses build the eligible set; the strategy breaks the tie.

From a ringing interaction to a chosen agent
Interaction
call / chat
Queue members
staffed pool
Available now
live presence
Skill match
meets min level
Strategy
picks one
Tip · Presence is the live half

Membership and skills are configuration — you set them and they persist. Availability is live: an agent is only routable while their presence state is available (not break, acw — after-call work — or offline). Presence is covered with the supervisor wallboard in Chapter 16; here, just remember that a perfectly skilled, fully staffed queue still routes to nobody if no member is currently available.

CloudCX · Administrator & Training ManualCh. 10 · ACD
CloudCXCloudCX Administrator & Training Manual
Ch. 10 · ACD

10.2The Call Routing view

The Call Routing view is your read-only window onto the live ACD. The left panel, ACD queues, lists every queue you can see — each row shows the queue name, its strategy and channel, its priority, and whether it carries a wait-time SLA. (The same view also lists inbound DIDs and the SIP carrier trunks; those are covered in Chapter 11 — Telephony & the SBC.) Use this view to confirm that a queue exists, that it is pointed at the right channel, and that its strategy is what you intend before you start attaching skills and members.

admin.cloudcx.app/#routing
Voice
Call Routing7
IVR Builder
Recordings
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Call Routing// 04 — routing & inbound · live · /acd/queues
PRODAO
Routing & inbound

ACD queues

Live distribution lines that connect interactions to agents.

7 queuesRefresh

ACD queues

live · /acd/queues
QueueStrategy · channelPrioritySLA
SBSales — Billingpriority · voiceprio 10 max 45s
ESEspañol — Supportlongest_idle · voiceprio 5 max 60s
WCWeb Chat — Generalfewest_calls · webchatprio 0no SLA
DWDefault Webchatlongest_idle · webchatprio 0no SLA
1
2
3
4
5
  1. Call Routing — the active item in the Voice nav group; the count badge is the number of queues.
  2. ACD queues panel — one row per queue you can see, refreshed live from /acd/queues.
  3. Strategy · channel — the queue’s distribution strategy and the channel it serves (any when unrestricted).
  4. Priorityprio N; higher wins when several queues compete for the same agents.
  5. SLA pillmax Ns when a wait ceiling is set, or no SLA when max_wait_seconds is blank.
The Call Routing view’s ACD queues panel — name, strategy, channel, priority and SLA at a glance.
Note · This panel is read-only

The Call Routing view displays queues; it does not create or edit them. The Default Webchat row is a platform-level queue the system creates automatically the first time a web chat needs routing — it has no owning tenant and uses the longest_idle strategy. To create your own queues, set strategies, and attach skills and members, use the ACD API described next (or your provisioning tooling that wraps it).

CloudCX · Administrator & Training ManualCh. 10 · ACD
CloudCXCloudCX Administrator & Training Manual
Ch. 10 · ACD

10.3Queues — the waiting lines

A queue is a waiting line for one channel with a configured distribution strategy. Every interaction that needs an agent is routed through exactly one queue. A queue can be tenant-scoped (owned by a single customer and visible only to them) or platform-level (no owning tenant — the shared default lines). The queue itself carries no agents and no skills directly; those are attached as members (§ 10.5) and required skills (§ 10.4).

10.3.1Queue fields

namestring, 1–255
Human-readable queue name, e.g. Sales — Billing. Required.
descriptiontext · optional
Free-text note about the queue’s purpose; shown to admins only.
strategyenum
How the queue picks the next agent: longest_idle (default), round_robin, fewest_calls or priority. See § 10.6.
channelstring, ≤32
The channel this queue serves — voice, webchat, etc. Defaults to any (unrestricted).
priorityint · default 0
Higher = more important when several queues compete for the same agents. Used to order queues; it does not change skill matching.
max_wait_secondsint · optional
Optional SLA: the maximum seconds an interaction should wait before overflow/escalation. Blank = no SLA.
tenant_iduuid · optional
Owning tenant. Blank = a platform-level/shared queue. Tenant admins are pinned to their own tenant; only platform admins may target another tenant or leave it blank.

10.3.2Creating a queue

  1. Choose the channel. Decide whether this queue serves voice, webchat or any channel. One queue serves one channel; create separate queues for voice and chat even if the same team staffs both.
  2. Pick the strategy. Default to longest_idle for fair rotation. Choose another only when you have a specific reason (§ 10.6 has a decision guide).
  3. Set priority and SLA. Leave priority at 0 unless this queue must out-rank others for shared agents. Set max_wait_seconds if the queue has a wait-time target.
  4. Create the queue. POST the queue to /acd/queues. The response includes the new queue’s id — you’ll need it to attach members and skills.
  5. Attach skills and members. A bare queue routes to all its members. Add required skills (§ 10.4) and staff agents (§ 10.5) to make it useful.
Create a voice queue (admin token required)
# POST /api/v1/acd/queues — returns the created queue incl. its id.
curl -X POST https://cloudcx.app/api/v1/acd/queues \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "Sales — Billing",
        "description": "Billing & account questions for the sales line",
        "strategy": "priority",
        "channel": "voice",
        "priority": 10,
        "max_wait_seconds": 45
      }'
Warning · Deleting a queue cascades

Deleting a queue (DELETE /acd/queues/{id}) also removes all of its memberships and required-skill rows — the link rows are cascaded. It does not delete the skills themselves or the agents. Live interactions already routed to an agent are unaffected; only future routing through that queue stops. Re-create the queue and you must re-attach members and skills.

Tip · Update in place with PATCH

To change a queue’s strategy, priority or SLA later, PATCH /acd/queues/{id} with only the fields you want to change — unset fields are left untouched. Switching the strategy takes effect on the next interaction; in-flight routing is never disturbed.

CloudCX · Administrator & Training ManualCh. 10 · ACD
CloudCXCloudCX Administrator & Training Manual
Ch. 10 · ACD

10.4Skills — competency tags

A skill is a competency tag — billing, spanish, tier-2, retentions. Skills do two jobs: agents hold a skill at a proficiency level, and queues require a skill at a minimum level. An agent is eligible for a skill-gated queue only when they hold every required skill at or above the queue’s minimum. Like queues, a skill can be tenant-scoped or platform-level.

Skill

The competency itself: a name (e.g. billing) and optional description. Created once, then referenced by agents and queues.

Agent skill

An agent has a skill at a level1 = basic, higher = stronger. One row per agent-skill pair.

Queue skill

A queue requires a skill at a min level. An agent must meet it to be eligible for that queue.

Levels

Levels are simple integers (≥ 1). They gate eligibility, and for the priority strategy they also rank who gets the interaction first.

10.4.1Creating skills and granting them to agents

  1. Define the skill once. POST /acd/skills with a name (and optional description). The response returns the skill’s id.
  2. Grant the skill to agents. For each agent, POST /acd/agents/{user_id}/skills with the skill_id and a proficiency level (default 1). One grant per agent-skill pair.
  3. Require the skill on a queue. POST /acd/queues/{queue_id}/skills with the skill_id and a min_level (default 1). Now only agents who hold the skill at that level or above are eligible.
  4. Verify with stats. Pull /acd/queues/{queue_id}/stats and confirm the required skill is listed and that members / available_now look right (§ 10.7).
Define a skill, grant it to an agent, and require it on a queue
# 1) Create the skill (returns its id, here $SKILL).
curl -X POST https://cloudcx.app/api/v1/acd/skills \
  -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
  -d '{ "name": "billing", "description": "Billing & invoicing competency" }'

# 2) Grant the skill to an agent at level 3.
curl -X POST https://cloudcx.app/api/v1/acd/agents/$AGENT/skills \
  -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
  -d '{ "skill_id": "'$SKILL'", "level": 3 }'

# 3) Require the skill on a queue at min_level 2.
curl -X POST https://cloudcx.app/api/v1/acd/queues/$QUEUE/skills \
  -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
  -d '{ "skill_id": "'$SKILL'", "min_level": 2 }'
Note · One grant per pair — re-granting conflicts

An agent can hold a given skill only once, and a queue can require a given skill only once. Re-posting the same agent-skill or queue-skill pair returns 409 Conflict. To change a level, remove the existing grant (DELETE the matching skill row) and re-add it with the new level — the API treats the pair as a set membership, not an upsert.

Warning · Deleting a skill removes it everywhere

Deleting a skill (DELETE /acd/skills/{id}) cascades to every agent grant and every queue requirement for that skill. Queues that required it lose the requirement silently — which can widen their eligible set. Review which queues require a skill before you delete it.

CloudCX · Administrator & Training ManualCh. 10 · ACD
CloudCXCloudCX Administrator & Training Manual
Ch. 10 · ACD

10.5Queue membership — staffing the line

A queue member is an agent staffed on a queue. Membership defines the candidate pool: only members can receive a queue’s interactions. The eligible set is then this pool narrowed to those who are available now and who meet every required skill. An agent can be a member of many queues; a queue can have many members.

The figure below shows the conceptual Queue members management surface — the staffed agents on a queue with their live presence and the queue’s required skills alongside, exactly the join the ACD performs at route time. (Membership and skill grants are applied through the ACD API; this is how they read back when assembled for an admin.)

admin.cloudcx.app/#routing/queue/sales-billing
Voice
Call Routing7
IVR Builder
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Sales — Billing// priority · voice · prio 10
PRODAO
Queue · members

Staffed agents

+ Add member

Members

3 staffed
AgentPresencebilling
PRPriya R. available lvl 3
TMTom M. acw lvl 2
L6Léo D. available lvl 1

Required skills

1 gate
billing
min level required
≥ 2
Eligible = member & available & billing ≥ 2.
Manage skills Queue stats
1
2
3
4
5
  1. Add member — staffs an agent on the queue (POST /acd/queues/{id}/members).
  2. Member roster — the candidate pool; each row is one staffed agent.
  3. Presence — live state; only available agents are routable (acw, break, offline are not).
  4. Skill level held — the agent’s billing level; below the queue’s min (red) means ineligible.
  5. Required skills — the gate this queue applies (billing ≥ 2).
A queue’s staffed agents shown beside its required skills — the exact join the ACD evaluates at route time.

Reading this example: Priya is a member, available, and holds billing at level 3 — eligible. Tom holds billing at level 2 (meets the gate) but is in after-call work, so he is not routable right now. Léo is available but holds billing only at level 1, below the queue’s min_level of 2 — ineligible. So at this instant the queue’s eligible set is just {Priya}.

10.5.1Adding and removing members

Staff and unstaff an agent on a queue
# Add a member (409 if already a member of this queue).
curl -X POST https://cloudcx.app/api/v1/acd/queues/$QUEUE/members \
  -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
  -d '{ "user_id": "'$AGENT'" }'

# Remove a member (404 if the membership does not exist).
curl -X DELETE https://cloudcx.app/api/v1/acd/queues/$QUEUE/members \
  -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
  -d '{ "user_id": "'$AGENT'" }'
Note · Both queue and agent must be in scope

You can only manage membership on a queue your account may access, and you can only add an agent your account may access. A platform admin can staff any agent on any queue; a tenant admin is confined to their own tenant’s queues and agents. A queue/agent outside your scope reads back as 404 Not found.

CloudCX · Administrator & Training ManualCh. 10 · ACD
CloudCXCloudCX Administrator & Training Manual
Ch. 10 · ACD

10.6Routing strategies

Once the eligible set is built (members who are available and meet every required skill), the queue’s strategy decides which one of them actually gets the interaction. CloudCX ships four strategies. They differ only in how they pick within the eligible set — none of them changes who is eligible.

Table 10.1 — The four routing strategies
StrategyPicksTie-breakBest for
longest_idleThe agent who has been available the longest (oldest presence timestamp).— (the default)Fair rotation; the standard, even-handed choice.
round_robinThe next agent in a stable rotation, advanced by a per-queue cursor on every route.Stable sort by agent idPredictable, even cycling through a fixed roster.
fewest_callsThe agent currently handling the fewest live interactions (least loaded).Longest idleLoad balancing when handle times vary widely.
priorityThe agent with the highest skill proficiency (largest skill level).Longest idleSend the hardest work to your strongest agents first.

10.6.1longest_idle (the default)

Among the eligible agents, CloudCX picks the one whose presence has been available the longest. Each time an agent changes state — including flipping back to available after wrap-up — their “available since” timestamp is reset. So “longest idle” means “least recently active”, which spreads work evenly without needing a separate counter. This is the fair, deterministic default and the same idea CloudCX uses for basic chat assignment.

10.6.2round_robin

The eligible agents are placed in a stable order (sorted by their id) and a per-queue cursor advances by one on every route, wrapping around the roster. The result is a predictable, even cycle: agent A, then B, then C, then back to A. If the cursor store is briefly unavailable, the queue safely falls back to longest_idle for that route, so a hiccup never stalls routing.

10.6.3fewest_calls

CloudCX counts each eligible agent’s live interactions (their currently open/assigned conversations) and picks the least loaded. Ties are broken by longest-idle. This balances concurrent load rather than raw turn order — useful where some interactions run long and you don’t want a busy agent handed yet another one while a free colleague sits idle.

10.6.4priority

CloudCX orders eligible agents by their highest skill level and picks the strongest first, breaking ties by longest-idle. Pair this with a required-skill gate to build an expertise ladder: the gate decides who may take the work, and the priority strategy decides that your most proficient eligible agent takes it first.

Same eligible set, four different picks
Eligible
A · B · C
longest_idle
→ longest free
round_robin
→ next in cycle
fewest_calls
→ least busy
priority
→ highest level
Tip · Which strategy should I pick?

Start with longest_idle — it is fair and needs nothing extra. Move to fewest_calls when agents juggle several concurrent chats and load matters more than turn-taking. Use priority when skill level should decide order (escalations, VIP lines). Reach for round_robin only when you need a strictly predictable cycle through a fixed roster.

Note · No eligible agent? Routing returns nobody — gracefully

If a queue is missing, no member is available, or nobody meets the required skills, the ACD returns no agent rather than erroring — routing sits on the live request path and is built to never break it. The interaction is simply left unassigned for the caller to fall back on (hold, voicemail, overflow). A queue that “routes to nobody” almost always means nobody available or an over-tight skill gate, not a fault.

CloudCX · Administrator & Training ManualCh. 10 · ACD
CloudCXCloudCX Administrator & Training Manual
Ch. 10 · ACD

10.7Queue stats — staffing & live availability

The queue stats endpoint gives you a one-shot snapshot of a queue’s health: its routing config, how many agents are staffed, how many of those are available right now, and the skills it requires. It is the fastest way to answer “why isn’t this queue routing?” — if available_now is 0, the queue has nobody to route to regardless of how it is configured.

admin.cloudcx.app/#routing/queue/sales-billing/stats
Voice
Call Routing7
Recordings
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Sales — Billing// queue stats · live · /acd/queues/{id}/stats
PRODAO
Queue · stats

Sales — Billing

priority · voice · prio 10 · SLA 45s

Members
3
staffed
Available now
1
routable
Strategy
priority
picks highest level
Channel
voice
PSTN

Required skills

1 gate
billing ≥ 2 Only members holding billing at level 2+ are eligible.
1
2
3
4
  1. Members — agents staffed on the queue (members in the stats payload).
  2. Available now — staffed members whose live presence is available (available_now).
  3. Strategy & channel — the queue’s configured routing strategy and channel.
  4. Required skills — each gate as skill ≥ min_level; empty means the queue is open to all members.
A queue’s live stats: 3 staffed, 1 available, priority strategy, gated on billing ≥ 2.

The stats payload is small and read-mostly — it is safe to poll for a live wallboard. Note that available_now is a count of members who are available, computed against live presence; it never fails on a cold cache (it just reports 0 availability rather than erroring).

Read a queue’s live stats
# GET /api/v1/acd/queues/{id}/stats
curl -s https://cloudcx.app/api/v1/acd/queues/$QUEUE/stats \
  -H "Authorization: Bearer $TOKEN"

# → snapshot of staffing, live availability and routing config:
{
  "queue_id": "…", "name": "Sales — Billing",
  "strategy": "priority", "channel": "voice",
  "members": 3, "available_now": 1,
  "required_skills": [ { "skill_id": "…", "min_level": 2 } ]
}
Tip · Two numbers to watch

For day-to-day health, watch available_now against members. members high but available_now at 0 = nobody signed in / everyone on break. Both healthy but callers still wait = your skill gate may be excluding everyone who is available (see the next section’s worked example).

CloudCX · Administrator & Training ManualCh. 10 · ACD
CloudCXCloudCX Administrator & Training Manual
Ch. 10 · ACD

10.8Worked example — Spanish billing, tier-2 first

Let’s build a skills-based voice queue end-to-end. The goal: Spanish-speaking billing calls should go only to agents who speak Spanish and know billing, and the most experienced eligible agent should answer first. We’ll require two skills and use the priority strategy so seniority decides order.

Queue
Español — Billing · channel voice
Strategy
priority (highest skill level first)
Required skills
spanish ≥ 1 · billing ≥ 2
SLA
max wait 45s

Three agents are staffed on the queue. Their skills:

Table 10.2 — Staffed agents and the eligibility check
AgentspanishbillingPresenceEligible?
Priya R.lvl 2lvl 3 available yes (billing 3)
Tom M.lvl 4 available no — no spanish
Léo D.lvl 3lvl 2 available yes (billing 2)

Who gets the next Spanish billing call? Tom is out — he meets billing but holds no spanish skill, so he fails the gate. Priya and Léo both pass (Spanish ≥ 1 and billing ≥ 2). With the priority strategy, CloudCX ranks the eligible pair by their highest skill level: Priya’s top level is 3 (billing), Léo’s top level is 3 (spanish) — a tie at 3, broken by longest-idle. So the call goes to whichever of Priya or Léo has been available longer. Had we used longest_idle instead, the level ranking would be ignored and the longest-available of the two would win directly.

10.8.1Build it step by step

  1. Create the queue. POST /acd/queues with name Español — Billing, channel: "voice", strategy: "priority", max_wait_seconds: 45. Note the returned id as $QUEUE.
  2. Create the two skills. POST /acd/skills for spanish and again for billing; capture their ids as $SPA and $BILL.
  3. Grant skills to agents. Priya: spanish=2, billing=3. Tom: billing=4 only. Léo: spanish=3, billing=2. Each is a POST to /acd/agents/{user_id}/skills.
  4. Require both skills on the queue. POST /acd/queues/$QUEUE/skills twice: {skill_id:$SPA, min_level:1} and {skill_id:$BILL, min_level:2}.
  5. Staff the queue. Add Priya, Tom and Léo as members via POST /acd/queues/$QUEUE/members.
  6. Verify with stats. GET /acd/queues/$QUEUE/stats. Expect members: 3, two required skills, and available_now equal to however many of the three are currently available.
Require both skills on the queue (steps 4)
# spanish at any level (≥1).
curl -X POST https://cloudcx.app/api/v1/acd/queues/$QUEUE/skills \
  -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
  -d '{ "skill_id": "'$SPA'", "min_level": 1 }'

# billing at level 2 or better.
curl -X POST https://cloudcx.app/api/v1/acd/queues/$QUEUE/skills \
  -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
  -d '{ "skill_id": "'$BILL'", "min_level": 2 }'
Note · Every required skill must be met — it is an AND, not an OR

A queue with two required skills admits only agents who satisfy both gates. Tom’s strong billing level cannot compensate for a missing spanish skill. If you intended “Spanish or billing”, you would model that as two separate queues, not one queue with two requirements.

CloudCX · Administrator & Training ManualCh. 10 · ACD
CloudCXCloudCX Administrator & Training Manual
Ch. 10 · ACD

10.9Practice & recap

Try it · Build and prove a skills-based queue

In a non-production environment, build a small queue and prove the eligibility logic with live presence:

  1. Create a webchat queue Tier-2 Support with strategy priority and a required skill tier2 ≥ 2.
  2. Staff three agents. Grant tier2 at levels 3, 2 and 1 respectively.
  3. Pull queue stats. Confirm members = 3 and the required skill reads tier2 ≥ 2.
  4. Have all three set themselves available. Predict who would be picked, then route a test chat — the level-1 agent must be excluded, and the strongest of the remaining two should answer first.
  5. Switch the queue to longest_idle (PATCH). Route again and confirm the pick now ignores level and follows availability order instead.
  6. Put the level-3 agent on break and re-check stats: available_now should drop by one, and routing should skip them.

Success looks like: the under-level agent is never chosen, the strategy visibly changes who answers, and available_now tracks presence in real time.

Tip · A queue-build checklist

For every new queue, in order: (1) create the queue with the right channel and strategy, (2) create any skills it needs, (3) grant those skills to agents at sensible levels, (4) require the skills on the queue at their minimums, (5) staff the members, (6) verify with stats and a test interaction.

10.9.1Chapter recap

Note · Where to go next

Queues decide who answers; Chapter 11 — Telephony & the SBC covers how the call gets to a queue — inbound DIDs, SIP carrier trunks and least-cost routing. Live agent presence and the supervisor wallboard that drives available_now are covered in Chapter 16 — Supervision & quality.

CloudCX · Administrator & Training ManualCh. 10 · ACD
CloudCXCloudCX Administrator & Training Manual
Ch. 11 · Outbound & DNC
Chapter 11

Outbound — campaigns, dialer modes and DNC

An outbound campaign is a list of contacts that CloudCX dials on your behalf and connects to agents. The platform ships three classic dialer modespreview, progressive and predictive — a closed-loop abandonment-rate guard that keeps predictive dialing inside the legal cap, per-contact retry and disposition handling, optional calling-window schedules, and a tenant-scoped Do-Not-Call (DNC) list the dialer can never call past. This chapter shows you how to build a campaign, choose and tune its mode, load contacts, run it while watching the live wallboard, manage the DNC list, and walks a complete worked campaign end-to-end.

Where you do this work

Everything in this chapter happens in the admin console at admin.cloudcx.app/campaigns, under the Voice group in the left navigation. The page has two tabs — CampaignsDo-Not-Call — and a per-campaign detail view that opens when you click a campaign row. Creating campaigns, adding contacts and editing the DNC list are admin/supervisor actions; any signed-in agent can read live stats and (in preview mode) drive their own dials.

11.1The three dialer modes

Every campaign carries one dial_mode. The mode decides who initiates the call and how aggressively the dialer paces — from one-at-a-time, agent-driven previews to ratio over-dialing that places more calls than there are free agents. Pick the mode to match the campaign’s sensitivity and the size of the agent pool.

Preview

The agent sees the next contact before any call is placed and clicks to dial. No auto-dialing. Highest agent control; lowest throughput. Best for high-value or complex calls.

Progressive

One live line per available agent. The system dials a number the moment an agent is free and bridges on answer. No abandoned calls by design; steady throughput.

Predictive

Ratio over-dial: places lines per agent × free agents calls, anticipating no-answers. Highest throughput, governed by the abandonment-rate guard (§ 11.4).

Who places the call, and the pacing per available agent, by mode
Previewagent clicks · 0 auto
Progressive1 line / agent
Predictiveceil(lines × agents)

All three share the same plumbing underneath. The dialer always checks the DNC list before placing a call, always honours the campaign’s calling window if one is set, always enforces max attempts and retry minutes, and always mirrors live counters to the supervisor wallboard. Only the pacing differs.

ModeWho dialsLines paced per tickAbandon riskUse it for
PreviewThe agent (screen-pop → click)0 (publishes the next contact only)NoneHigh-value, regulated or complex calls; small lists.
ProgressiveThe dialer, on agent-free1 per available agentNone (never more calls than agents)Steady B2B / collections / renewals.
PredictiveThe dialer, over-dialingceil(lines_per_agent × agents)Managed — capped by the abandon guardLarge lists where throughput matters and a small abandon rate is acceptable.
Table 11.1 — Dialer modes at a glance.
Tip · Start progressive, graduate to predictive

When in doubt, run a new list progressive first. It is self-pacing (one call per free agent) and can never abandon a customer. Once you know the list’s connect rate, switch to predictive and nudge lines per agent up to lift throughput — the abandon guard will hold you under the cap.

CloudCX · Administrator & Training ManualCh. 11 · Outbound & DNC
CloudCXCloudCX Administrator & Training Manual
Ch. 11 · Outbound & DNC

11.1.1The campaign lifecycle

A campaign moves through four states. You drive the transitions from the detail view’s run controls; the dialer also auto-completes a campaign when there is genuinely no work left. Knowing the state machine explains exactly when the Start, Pause and Stop buttons are available.

Campaign state machine and the controls that drive each edge
draftcreated · no runner
▶ Start
runningrunner paces calls
⏸ Pause
pausedrunner cancelled
paused▶ Resume → running
⏹ Stop
completedterminal · hard stop
auto
running → completedno work left
draft
Created and editable; no dialer runner is attached yet. Add contacts here.
running
A per-campaign runner task is live, pacing calls every few seconds and refreshing stats.
paused
Runner cancelled; contact states frozen. Resume to continue exactly where it left off.
completed
Terminal. Reached by Stop, or automatically when all contacts are worked.
Note · A restart resumes running campaigns

The dialer is resilient: if the platform restarts, every campaign still marked running has its runner re-spawned automatically on boot, and live counters self-heal from the database. You do not need to re-start a campaign after a deploy — only after you have explicitly paused or stopped it.

CloudCX · Administrator & Training ManualCh. 11 · Outbound & DNC
CloudCXCloudCX Administrator & Training Manual
Ch. 11 · Outbound & DNC

11.2The Campaigns list

Open Outbound Campaigns from the Voice group. The Campaigns tab lists every campaign for the current tenant, newest first. Each row summarises one campaign — its mode, status, ACD queue and its attempt/retry policy — and clicking a row opens its detail view (§ 11.5). The + New campaign button reveals the inline create form (§ 11.3).

admin.cloudcx.app/campaigns
Operate
Dashboard
Voice
Queues
Outbound Campaigns
Governance
Do-Not-Call
DIALER CORE ONLINE
Phase 4 · voice core
Outbound Campaigns// predictive · progressive · preview dialing
PRODAO
Campaigns Do-Not-Call
// Outbound dialer · live

Campaigns

Create and operate outbound calling campaigns and watch the dialer live.

Refresh+ New campaign

All campaigns

3 campaigns
NameModeStatusQueueCounts
Q3Q3 Renewals predictive Running Renewals AU max 3 · retry 60m open ›
WBWinback Q2 progressive Paused Sales max 4 · retry 120m open ›
VRVIP Renewals preview Draft — none — max 3 · retry 60m open ›
1
2
3
4
5
  1. Outbound Campaigns — the active item in the Voice nav group. The dark footer shows the dialer core health.
  2. Tabs & + New campaign — switch between the Campaigns list and the Do-Not-Call list; the gradient button reveals the inline create form.
  3. Status — the live state pill (Draft / Running / Paused / Completed); a running campaign’s pill pulses.
  4. Mode — the campaign’s dial_mode (preview / progressive / predictive).
  5. Counts — the attempt policy at a glance: max attempts and retry minutes. Click open › for the full detail view.
The Campaigns list, with the create action and key columns highlighted.
Note · Platform admins see a tenant selector

If you sign in as a platform admin (no fixed tenant), a Tenant selector appears in the top bar. Pick a tenant before you create a campaign or add a DNC number — the selection scopes the list and pins the new record to that tenant. A tenant-scoped admin has no selector; everything is automatically pinned to their tenant.

CloudCX · Administrator & Training ManualCh. 11 · Outbound & DNC
CloudCXCloudCX Administrator & Training Manual
Ch. 11 · Outbound & DNC

11.3Creating a campaign

Click + New campaign to expand the New campaign form inline at the top of the list. Only the Name is strictly required; every other field has a working default. An optional calling window block lets you restrict dialing to certain days and hours in a chosen timezone.

admin.cloudcx.app/campaigns
Voice
Outbound Campaigns
DIALER CORE ONLINE
Phase 4 · voice core
Campaigns// new campaign
PRODAO

New campaign

draft
Name *Q3 Renewals
Dial modePredictive
ACD queueRenewals AU
Caller ID+61 2 5550 0100
Lines per agent1.8
Max attempts3
Retry minutes60
Calling window / schedule (optional)
TimezoneAustralia/Sydney
Start (HH:MM)09:00
End (HH:MM)18:00
MonTueWedThuFriSatSun
Create campaignCancel
1
2
3
4
5
6
  1. Name * — the only required field; the campaign’s display name (e.g. Q3 Renewals).
  2. Dial mode — preview / progressive / predictive (§ 11.1). Defaults to preview.
  3. ACD queue — the queue whose available members the dialer staffs answered calls into; also defines the agent pool for pacing.
  4. Lines per agent & attempts — predictive/progressive pacing ratio, plus max attempts and retry minutes.
  5. Calling window — optional schedule: timezone, daily start/end and the allowed weekdays.
  6. Create campaign — validates and saves; the campaign appears at the top of the list in draft.
The New campaign form — core settings, pacing, and an optional calling window.

11.3.1Step by step

  1. Open the form. On the Campaigns tab, click + New campaign. The panel expands inline and focuses the Name field.
  2. Name it and pick a mode. Enter a Name, then choose a Dial mode. If unsure, choose progressive (see § 11.1).
  3. Choose the queue and caller ID. Pick an ACD queue so answered calls have agents to bridge to, and set the outbound Caller ID the customer will see.
  4. Set the pacing. For predictive/progressive set Lines per agent (1.0 = progressive; > 1.0 = predictive over-dial). Set Max attempts and Retry minutes for the retry policy.
  5. Optionally add a calling window. Tick Calling window / schedule, then set Timezone, Start/End (HH:MM) and the allowed days. Outside the window the dialer idles.
  6. Create. Click Create campaign. The new campaign lands at the top of the list in draft. Open it to add contacts (§ 11.6) before you start.
Warning · A queue-less campaign can dial but cannot connect

The ACD queue is technically optional, but it is what gives the dialer agents to bridge answered customers to. A campaign with no queue and no available agents will place no progressive/predictive calls (pacing is driven by the available-agent count), and any preview dial that connects will have nowhere to land. Always attach a queue for auto-dialed modes.

CloudCX · Administrator & Training ManualCh. 11 · Outbound & DNC
CloudCXCloudCX Administrator & Training Manual
Ch. 11 · Outbound & DNC

11.3.2Campaign field reference

Every field the campaign form accepts, its constraint, and what it controls at runtime:

Namename
Required. 1–255 chars. The campaign’s display name in the list and detail header.
Dial modedial_mode
One of preview / progressive / predictive. Decides who dials and the pacing.
ACD queuequeue_id
Optional. The queue whose available members are the agent pool; answered calls bridge to it.
Caller IDcaller_id
Optional, up to 64 chars. The outbound caller-ID number presented on the customer leg.
Lines per agentlines_per_agent
Must be > 0; default 1.0. The predictive over-dial ratio. 1.0 behaves like progressive.
Max attemptsmax_attempts
Integer ≥ 0; default 3. A contact is retried until its attempts reach this ceiling.
Retry minutesretry_minutes
Integer ≥ 0; default 60. A no-answer/busy contact cools down this long before it is eligible again.
Scheduleschedule
Optional JSON calling window: timezone, days, start, end. Empty = always open.
Statusstatus
Lifecycle state. New campaigns start draft; driven by the run controls thereafter.

11.3.3The calling-window schedule

The optional schedule object restricts when the dialer may place calls. All keys are optional; the dialer evaluates “now” in the schedule’s timezone and idles whenever the current moment falls outside the window. A missing or malformed schedule fails open — the campaign dials at all times rather than freezing silently.

KeyType / exampleMeaning
timezoneIANA name, e.g. Australia/SydneyThe window is evaluated in this zone. Unknown zone → falls back to UTC.
days["mon","tue",…] or 0..6Allowed weekdays (0 = Mon). Empty/absent = every day.
start"09:00" (24h)Daily window open time. Needs end too.
end"18:00" (24h)Daily window close time. start > end means an overnight window (e.g. 20:00–06:00).
Table 11.2 — Keys of the optional calling-window schedule.
A typical business-hours calling window (stored on the campaign)
"schedule": {
    "timezone": "Australia/Sydney",
    "days": ["mon", "tue", "wed", "thu", "fri"],
    "start": "09:00",
    "end": "18:00"
}
Tip · Use the customer’s timezone, not yours

Calling-hour rules are about the customer’s local time. Set the schedule timezone to the region you are dialing — if you run a campaign from Singapore into Australian numbers, set Australia/Sydney so “09:00–18:00” means the customer’s morning-to-evening, not yours.

CloudCX · Administrator & Training ManualCh. 11 · Outbound & DNC
CloudCXCloudCX Administrator & Training Manual
Ch. 11 · Outbound & DNC

11.4Pacing & the abandonment-rate guard

In predictive mode the dialer deliberately places more calls than it has free agents, betting that some won’t answer. When that bet is wrong — a customer answers but no agent is free — the call is abandoned. Regulators cap the abandonment rate (commonly 3%), so CloudCX runs a real, closed-loop guard that throttles pacing back to one line per agent whenever the recent rate climbs to the ceiling, then lets it resume once it recovers.

The predictive pacing decision, each tick
Count free agentslive presence
Budgetceil(lines × agents)
Abandon guard?rate ≥ ceiling
Under ceilingplace full budget
vs
At/over ceilingback off → 1/agent
Recoversresume over-dial

11.4.1How the guard computes the rate

Abandoned calls and live connects are counted in a sliding window so the rate reflects recent pacing, not the campaign’s whole history. The rate is simply abandons over answered calls in that window:

# Recent abandonment rate, evaluated every tick
abandon_rate = abandons / (abandons + connects)   # over the sliding window

# The guard trips (pacing backs off to 1 line/agent) when BOTH hold:
answered = abandons + connects
if answered >= MIN_SAMPLE and abandon_rate >= MAX_RATE:
    budget = available_agents        # progressive fallback this tick
Ceiling (MAX_RATE)
Default 3% (0.03). Tunable platform-wide via DIALER_MAX_ABANDON_RATE.
Minimum sample
10 answered calls before the guard trusts the rate — it won’t throttle on a noisy 1-of-1.
Window
1 hour rolling. Older events age out, so the rate tracks the live floor.
Abandoned leg
Torn down immediately (no dead air) and re-queued for a retry like a no-answer.

The live abandon_rate and abandoned count are reported by the stats endpoint, so you can watch the guard work on the wallboard. For non-predictive campaigns these are always zero.

Warning · Abandonment-rate caps are a legal obligation

Many jurisdictions legally cap predictive-dialer abandonment (often at 3% of live answers, measured over a defined period). The guard helps you stay compliant, but it is your responsibility to set lines per agent sensibly and to confirm the ceiling matches your local rules. When in doubt, dial progressive — it cannot abandon a call.

11.4.2Answering-machine detection (AMD)

True predictive dialers also try to route detected voicemail away from agents so a person only ever joins a person. Answering-machine detection is a capability of the underlying voice platform / carrier rather than something CloudCX can infer on its own, so the dialer does not fabricate an AMD result: there is a clearly-documented, idle-safe hook where machine-leg routing will gate once AMD is provisioned. Until then every answer is treated as a live human.

Note · Tuning lines per agent

Lines per agent is the single biggest lever on predictive throughput. Higher ratios reach more customers per agent-hour but push the abandon rate up; the guard then claws pacing back. A common starting point is 1.5–2.0 for a list with a moderate connect rate. Raise it gradually and watch Connect rate and abandon rate together on the wallboard.

CloudCX · Administrator & Training ManualCh. 11 · Outbound & DNC
CloudCXCloudCX Administrator & Training Manual
Ch. 11 · Outbound & DNC

11.5Run controls & the live wallboard

Clicking a campaign row opens its detail view: a header with the run controls, a live dialer stats panel, and the contacts tools (§ 11.6). While the campaign is running the stats panel polls every few seconds and the progress bar tracks connected / dialed. The status pill, the controls and the polling all stay in sync with the authoritative server state — if the dialer auto-completes the campaign, the page reflects it without a refresh.

admin.cloudcx.app/campaigns
Voice
Outbound Campaigns
DIALER CORE ONLINE
Phase 4 · voice core
Q3 Renewals// live dialer
PRODAO
← back to campaigns

Q3 Renewals   Running

predictive dialing · queue: Renewals AU · caller ID: +61 2 5550 0100 · lines/agent: 1.8 · max attempts: 3 · retry: 60m

▶ Start⏸ Pause⏹ Stop

Live dialer stats

polling every 4s
Connected
128
live
Dialed
540
Connect rate
23.7%
Remaining
460
Progress · connected / dialed128 connected of 540 dialed · 1,000 total

42

Pending

9

Dialing

261

No answer

88

Busy

31

Failed

14

DNC

1
2
3
4
5
6
  1. Run controls — Start / Pause / Stop. They enable and re-label (Start ↔ Resume) to match the lifecycle (§ 11.1.1).
  2. Poll state — shows polling every 4s while running; switches to not polling when paused/completed.
  3. Connected — live count of contacts an agent actually talked to (the live KPI tile pulses).
  4. Connect rate — connected ÷ dialed, the headline efficiency number.
  5. Progress bar — connected of dialed, with the campaign’s total contacts alongside.
  6. State breakdown — per-status counts (Pending, Dialing, No answer, Busy, Failed, DNC, Completed, Total).
The campaign detail view — run controls and the live dialer wallboard.
CloudCX · Administrator & Training ManualCh. 11 · Outbound & DNC
CloudCXCloudCX Administrator & Training Manual
Ch. 11 · Outbound & DNC

11.5.1Running a campaign — step by step

  1. Open the campaign. From the list, click the row to open its detail view. Confirm the queue, caller ID and pacing in the header are what you intend.
  2. Confirm contacts are loaded. Scroll to the Contacts panel. The Total in the live stats should match your list size; flagged DNC contacts are already excluded from dialing.
  3. Start. Click ▶ Start. The status pill flips to Running, the runner spins up, and the stats panel begins polling every few seconds.
  4. Watch the wallboard. Track Connect rate and (predictive) the live abandon rate. Pacing follows your available agents in the queue.
  5. Pause if needed. Click ⏸ Pause to hold; contact progress is frozen. Click ▶ Resume to continue exactly where it left off.
  6. Stop when done. Click ⏹ Stop for a hard stop (marks the campaign Completed). Otherwise the dialer auto-completes once every contact is worked.

11.5.2Live-stats field reference

The wallboard is driven by the live-stats feed. Every counter, and what it means:

FieldAPI keyMeaning
PendingpendingNot yet dialed, or cooling down for a retry.
DialingdialingA call is in flight right now (claimed, originating or ringing).
ConnectedconnectedAnswered and bridged to an agent. Terminal success.
No answerno_answerRang out / no user response (also where an abandoned predictive call lands for retry).
BusybusyLine busy or the call was rejected.
FailedfailedThe originate failed (bad number / gateway error) after exhausting attempts.
DNCdncNumber is on the Do-Not-Call list; never dialed.
CompletedcompletedWorked to the attempt limit without connecting, or dispositioned done by an agent.
Dialed (total)dialed_totalEverything that has left the pending pool — the connect-rate denominator.
Connect rateconnect_rateconnected ÷ dialed_total (0 when nothing dialed yet).
AbandonedabandonedPredictive only: answered-but-unstaffed calls in the live window.
Abandon rateabandon_ratePredictive only: abandoned ÷ answered over the window (feeds the guard).
Table 11.3 — Live dialer stats fields.
Note · Polling pauses with the tab

To save bandwidth the wallboard stops polling when you switch away from the browser tab and resumes (with an immediate refresh) when you return — provided the campaign is still running. The numbers you see on return are always the authoritative server counters, recomputed from the database, so they are correct even after a long gap.

CloudCX · Administrator & Training ManualCh. 11 · Outbound & DNC
CloudCXCloudCX Administrator & Training Manual
Ch. 11 · Outbound & DNC

11.6Contacts, retries & dispositions

Contacts are the leads a campaign dials. You load them in the detail view’s Add contacts box — one per line, either name,number or a bare number — and they appear in the paginated Contacts table with a live status, attempt count and disposition. Numbers already on the tenant’s DNC list are flagged dnc the moment they are imported, so the dialer skips them automatically.

admin.cloudcx.app/campaigns
Voice
Outbound Campaigns
DIALER CORE ONLINE
Phase 4 · voice core
Q3 Renewals// contacts
PRODAO

Add contacts

paste "name,number" per line — or just numbers
LeadsOne per line. Numbers on the DNC list are auto-flagged.
Add contacts

Contacts

AllPendingConnectedNo answerDNC
NumberNameStatusAttemptsDisposition
+61 411 000 001Jane Doe Connected1Renewed
+61 411 000 002John Roe No answer2
+61 411 000 003 Pending0
+61 411 000 044Opted-out Pty DNC0
+61 411 000 077Late Co Pending1Callback 14:30
← PrevNext →page 1 · rows 1–5
1
2
3
4
5
  1. Leads box — paste one lead per line: name,number or a bare number. The last comma-field is the number; the rest is the name.
  2. Add contacts — bulk-imports the leads and reports how many were added and how many were flagged DNC.
  3. Status filter — segment the table by dial status (All / Pending / Connected / No answer / Failed / DNC / Completed).
  4. Attempts — how many times the dialer has tried this contact; it stops at max attempts.
  5. Disposition — the outcome an agent recorded (e.g. Renewed), or a scheduled Callback time.
Adding leads and the live contacts table, with DNC-flagged and callback rows.
Tip · Names with commas are fine

The importer treats the last comma-separated field as the number and everything before it as the name, so Doe, Jane, +61 411 000 001 imports cleanly as name “Doe, Jane” with that number. Formatting of the number itself doesn’t matter — DNC and dialing both work on digits only.

CloudCX · Administrator & Training ManualCh. 11 · Outbound & DNC
CloudCXCloudCX Administrator & Training Manual
Ch. 11 · Outbound & DNC

11.6.1How retries and dispositions work

Every dial outcome runs through the same retry policy. A contact that didn’t connect is retried — left pending with a cooldown — until its attempts reach max attempts, at which point it is finalised. A connected contact is terminal. Agents (in preview mode) can also record a disposition and, optionally, schedule a callback.

A single contact’s outcome path
pendingdialable now
dialingattempts +1
answered + staffed→ connected ✓
no answer / busyattempts < max?
pending (retry)after retry_minutes
at max
completed / failedterminal

11.6.2Dispositions & callbacks (preview)

In preview mode an agent works one contact at a time: a screen-pop offers the next contact, the agent commits the dial, and after the call records a disposition (a short outcome code such as Renewed, Not interested or Wrong number). If the agent sets a callback time, the contact is re-queued so it surfaces again at that moment; otherwise it is marked completed. The disposition is shown in the contacts table and flows into reporting.

Disposition only
Records the outcome and marks the contact completed (agent has finished it).
Disposition + callback
Records the outcome and re-queues the contact pending to come due at the callback time.
Screen-pop (next)
The dialer publishes the next dialable contact per ready agent; nothing is auto-dialed in preview.
Agent commit (dial)
The agent triggers the call; it is DNC-checked again at commit and bridged to that agent on answer.
Note · The same row can’t be double-dialed

Within a campaign exactly one runner selects contacts, and a contact is atomically flipped from pending to dialing before any call is placed. A preview commit uses the same state-guard, so two agents (or an agent and the auto-dialer) can never grab the same contact — the second attempt simply finds it is no longer dialable.

CloudCX · Administrator & Training ManualCh. 11 · Outbound & DNC
CloudCXCloudCX Administrator & Training Manual
Ch. 11 · Outbound & DNC

11.7The Do-Not-Call (DNC) list

The Do-Not-Call list is the set of numbers the dialer must never call for a tenant. It is the platform’s compliance backstop: the dialer checks it before every call, contact import flags matches on arrival, and matching is done on digits only — so +61 411 000 044 and 0411000044 collide regardless of formatting. Open it from the Do-Not-Call tab.

admin.cloudcx.app/campaigns
Governance
Do-Not-Call
DIALER CORE ONLINE
Phase 4 · voice core
Do-Not-Call list// compliance · digits-matched
PRODAO
Campaigns Do-Not-Call

Add number

Number+61 411 000 044
Reason (optional)customer opted out
Add to DNC

Listed numbers

Filter by number…
NumberReasonAdded
+61 411 000 044customer opted out15 Jun 2026, 14:02Remove
0400 123 999imported suppression list12 Jun 2026, 09:30Remove
+61 2 5550 777710 Jun 2026, 16:48Remove
← PrevNext →page 1 · 3 shown
1
2
3
4
5
  1. Do-Not-Call tab — the compliance list, scoped to the current tenant; numbers here are never dialed.
  2. Number — the number to suppress. Stored as typed, matched on digits, so formatting is irrelevant.
  3. Add to DNC — adds a single number with an optional free-text reason.
  4. Filter — substring-search the listed numbers.
  5. Remove — deletes an entry; that number becomes dialable again on the next attempt.
The Do-Not-Call list — single add, filter, and the listed numbers.
CloudCX · Administrator & Training ManualCh. 11 · Outbound & DNC
CloudCXCloudCX Administrator & Training Manual
Ch. 11 · Outbound & DNC

11.7.1Bulk-loading a suppression list

For an imported opt-out or suppression list, use Bulk add: paste one number per line. Numbers already on the tenant’s list (compared on digits) are skipped, and the result tells you how many were added versus skipped — so re-running the same import is safe and idempotent.

  1. Open Bulk add. On the Do-Not-Call tab, expand the Bulk add panel (paste a list).
  2. Paste numbers. One number per line. An optional Reason is stamped on every number in the batch (e.g. imported suppression list).
  3. Add. Click Bulk add. The toast reports Added N · skipped M (already listed).
  4. Verify. Filter the listed numbers for a sample to confirm it landed. New imports also flag matching contacts in any existing campaign on their next load.

11.7.2How the DNC check protects every dial

DNC is enforced at three points, all tenant-scoped and digits-matched, so a listed number cannot slip through:

On import

When you add contacts, any number already on the DNC list is inserted with status dnc — it is never queued for dialing.

Before each dial

The runner re-checks the DNC list immediately before placing any call. A match is marked dnc and skipped.

On preview commit

An agent’s manual dial is DNC-checked at the moment of commit; a listed number is refused with a clear conflict.

Danger · The DNC check fails closed

If the DNC lookup itself cannot complete (for example a transient database error), the dialer treats the number as if it were listed and refuses to dial it. This is deliberate: it is always safer to skip a call than to risk dialing a number that may be suppressed. A skipped contact is simply retried on a later tick once the lookup recovers.

Warning · DNC is per-tenant

Each tenant has its own DNC list; one tenant’s suppressions never affect another’s campaigns. If you operate several tenants, maintain (or import) the appropriate suppression list into each one. Platform admins must pick the target tenant in the top bar before adding numbers.

CloudCX · Administrator & Training ManualCh. 11 · Outbound & DNC
CloudCXCloudCX Administrator & Training Manual
Ch. 11 · Outbound & DNC

11.8Worked example — the “Q3 Renewals” predictive campaign

Let’s run a real campaign end-to-end. The retention team has 1,000 Australian customers up for renewal. We’ll dial them predictive through the Renewals AU queue, only during Sydney business hours, retrying misses up to three times. Before we start we load the team’s opt-out list into DNC so nobody who has unsubscribed is ever called.

Campaign
Q3 Renewals · predictive · queue Renewals AU
Caller ID
+61 2 5550 0100
Pacing
1.8 lines/agent · max 3 attempts · 60-min retry
Window
Mon–Fri 09:00–18:00 Australia/Sydney
  1. Load the opt-out list first. On the Do-Not-Call tab, open Bulk add, paste the retention team’s opt-out numbers, reason Q3 opt-outs, and click Bulk add. Confirm the added / skipped toast.
  2. Create the campaign. Campaigns → + New campaign. Name Q3 Renewals, mode Predictive, queue Renewals AU, caller ID +61 2 5550 0100. Set Lines per agent 1.8, Max attempts 3, Retry minutes 60.
  3. Add the calling window. Tick Calling window, timezone Australia/Sydney, 09:0018:00, days Mon–Fri. Click Create campaign.
  4. Import the contacts. Open the new campaign, paste the 1,000 renewal leads (name,number) into Add contacts, click Add contacts. The toast reports e.g. “Added 1000 contacts (14 flagged DNC)” — those 14 opt-outs are already excluded.
  5. Start & supervise. Click ▶ Start. As agents in Renewals AU become available, the dialer over-dials at 1.8×. Watch Connect rate climb and keep an eye on the live abandon rate.
  6. Let the guard work. If a burst of answers with no free agents pushes the abandon rate to 3%, pacing automatically falls back to one line per agent until it recovers — no action needed from you.
  7. Wind down. Misses are retried after 60 minutes, up to 3 attempts. When every contact is worked the campaign auto-completes; or click ⏹ Stop to end it early.

11.8.1The same campaign via the API

Every console action is also an API call. Here is the campaign created, contacts added and the run started, using an admin token. Status is read from the live-stats endpoint.

Create the campaign, load contacts, start it, and read live stats
# 1) Create the campaign (returns the new campaign id).
curl -X POST https://cloudcx.app/api/v1/campaigns \
  -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
  -d '{
        "name": "Q3 Renewals",
        "dial_mode": "predictive",
        "queue_id": "<renewals-au-queue-id>",
        "caller_id": "+61255500100",
        "lines_per_agent": 1.8,
        "max_attempts": 3,
        "retry_minutes": 60,
        "schedule": { "timezone":"Australia/Sydney",
                       "days":["mon","tue","wed","thu","fri"],
                       "start":"09:00", "end":"18:00" }
      }'

# 2) Bulk-add contacts (numbers already on DNC come back flagged in `dnc`).
curl -X POST https://cloudcx.app/api/v1/campaigns/$CID/contacts \
  -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
  -d '{ "contacts": [ {"name":"Jane Doe", "number":"+61411000001"},
                       {"name":"John Roe", "number":"+61411000002"} ] }'
# → { "added": 2, "dnc": 0 }

# 3) Start the runner, then poll live stats.
curl -X POST https://cloudcx.app/api/v1/campaigns/$CID/start \
  -H "Authorization: Bearer $ADMIN_TOKEN"
curl https://cloudcx.app/api/v1/campaigns/$CID/stats \
  -H "Authorization: Bearer $ADMIN_TOKEN"
# → { "status":"running", "connected":128, "dialed_total":540,
#     "connect_rate":0.2370, "abandon_rate":0.018, "remaining":460, ... }
Note · Caller ID and digits

The dialer normalises numbers to digits (keeping a leading +) and applies the tenant’s outbound dial prefix if one is configured, so you can pass numbers in whatever format your CRM exports. Set a real, dialable caller ID the customer will recognise — it materially affects answer rates and is often a regulatory requirement.

CloudCX · Administrator & Training ManualCh. 11 · Outbound & DNC
CloudCXCloudCX Administrator & Training Manual
Ch. 11 · Outbound & DNC

11.9Practice & recap

Try it · Build, protect and run a practice campaign

In a non-production environment, run a small campaign end-to-end and prove each control:

  1. On the Do-Not-Call tab, add one number (reason practice opt-out). Then bulk-add a short list and confirm the added / skipped counts; re-run the same bulk add and confirm everything is now skipped.
  2. Create a progressive campaign Practice Run attached to a test queue with one agent. Set Max attempts = 2, Retry minutes = 1.
  3. Add five contacts — include the number you put on DNC. Confirm it imports with status DNC and is excluded from dialing.
  4. Make the test agent available, click Start, and watch the wallboard: confirm pacing is one live line per agent and the progress bar tracks connected / dialed.
  5. Let a contact go unanswered; after a minute confirm it returns to Pending and is retried, then finalises at 2 attempts.
  6. Switch the campaign to predictive with Lines per agent = 2.0, restart, and observe the live abandon rate; force a few abandons and watch pacing throttle back.
  7. Pause, then Resume, then Stop — and confirm a stopped campaign cannot be started again.

Success looks like: DNC numbers never dialed, retries honouring the cooldown, the abandon guard throttling predictive pacing, and the lifecycle controls behaving exactly as in § 11.1.1.

Tip · A clean campaign launch checklist

For every campaign, in order: (1) load/refresh the tenant’s DNC list, (2) create the campaign with the right mode, queue and caller ID, (3) set a sensible pacing and retry policy, (4) add a calling window in the customer’s timezone, (5) import contacts and confirm the DNC-flag count, (6) start with agents available and watch connect & abandon rates together.

11.9.1Chapter recap

Note · Where to go next

Outbound campaigns staff answered calls into ACD queues, so the natural companions are the inbound voice chapters: queues and routing, agent presence and the live supervisor wallboard. Outbound calls also produce CDRs and dispositions that feed the reporting and analytics covered later in the manual.

CloudCX · Administrator & Training ManualCh. 11 · Outbound & DNC
CloudCXCloudCX Administrator & Training Manual
Ch. 12 · The Voice channel
Chapter 12

The Voice channel — inbound, outbound, recording & screen-pop

Voice is the oldest channel in the contact centre and still the one customers reach for when something matters. In CloudCX a phone call is not a black box: it is a sequence of stages you configure and can reason about — the number the caller dialled (the DID), the IVR flow that greets and triages them, the ACD queue that holds them while it hunts for the right agent, and finally the agent whose softphone rings and bridges to the caller. This chapter walks that path end to end, then covers the two directions of traffic (inbound and outbound), call recording with AI transcription, and the screen-pop that gives the agent context the instant their phone rings.

Where you do this work

Inbound numbers, queues and outbound trunks live in the admin console at admin.cloudcx.app under PlatformCall Routing. Call flows have their own visual builder at VoiceIVR / Call Flows. The agent-side experience — the ringing softphone, screen-pop and outbound dialer — lives in the agent desktop at cloudcx.app/agent; supervisors monitor live calls from cloudcx.app/supervisor.

12.1How a call flows: DID → IVR → queue → agent

Every inbound call follows the same skeleton. The carrier delivers the call over a SIP trunk to the CloudCX session-border controller (the CloudCX SBC edge + CloudCX Switch media engine); CloudCX Switch looks up the dialled number in your DIDs and hands the call to whatever that DID points at — a flow, a queue, an extension or a mailbox. Most production numbers point at a flow: an IVR graph that greets the caller, checks business hours, offers a menu, and routes the chosen branch into an ACD queue. The queue parks the caller on hold music and rings the best available agent; on answer the two legs are bridged and the conversation begins. When either side hangs up, CloudCX writes a CDR (call detail record) and — if recording was on — files the audio and queues it for transcription.

The inbound call lifecycle — each box is a stage you configure
PSTNcarrier
SBC edgeCloudCX SBC + FS
DIDdialled number
IVR flowgreet · triage
QueueACD · hold
Agentring · bridge

The same machinery runs outbound, only in reverse: the agent (or the predictive dialer) originates a leg to the customer through a carrier trunk chosen by least-cost routing, and on answer it is bridged to the agent's softphone. Inbound and outbound therefore share the same engine, the same recording pipeline and the same CDR table — the only real difference is who places the first leg.

Table 12.1 — The objects that make up the Voice channel
ObjectWhat it isWhere you manage it
DIDAn inbound phone number you own, with a routing destination.Call Routing › Inbound DIDs
IVR flowA node graph that greets and triages a caller; published before it goes live.Voice › IVR / Call Flows
QueueAn ACD waiting line with a distribution strategy and required skills.Call Routing › ACD queues
SIP trunkA carrier gateway that carries calls to/from the PSTN.Call Routing › SIP carrier trunks
LCR rulePer-prefix rule choosing the cheapest/best trunk for outbound.Call Routing › Least-cost routing
CDRThe immutable record of a finished call (timings, cause, recording path).Reports / GET /calls/cdrs
Tip · build the destination before the DID

A DID can only route to something that already exists. Build (and publish) your IVR flow, or create your queue, before you point a number at it — the number picker only lists destinations that are live. The recommended order is: queue → flow → publish → DID.

CloudCXCloudCX Administrator & Training Manual
Ch. 12 · The Voice channel

12.2Inbound numbers — the DID and its destination

A DID (Direct Inward Dialing number) is a phone number your tenant owns. Each DID carries one destination: the thing an inbound call to that number is handed to. There are exactly four destination types, and choosing the right one is the single most important inbound decision you make:

Flow
flow
Hand the call to a published IVR flow (greeting, hours check, menu, queue, voicemail). This is the normal choice for a main line.
Queue
queue
Skip the IVR and drop the caller straight into an ACD queue. Good for a direct support hotline with no menu. CloudCX answers, plays hold music and rings an agent for up to 60 seconds.
Extension
extension
Bridge the call directly to one internal extension (e.g. a desk phone 1010). A personal direct line.
Voicemail
voicemail
Answer, play a greeting and record a message into a mailbox. Used for after-hours lines or unstaffed numbers.

The Inbound DIDs panel lives inside Call Routing alongside the ACD queues and the SIP trunks, so you can see the whole inbound picture on one screen.

admin.cloudcx.app
Platform
Call Routing
Channels
Voice
IVR / Call Flows
Campaigns
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Call Routing// ACD · DIDs · trunks · LCR
PRODAO
Platform

Inbound DIDs

Numbers you own and where each one routes.

+ Add DID

Inbound DIDs

4 numbers
NumberLabelRoutes toTypeStatus
+65 3159 1234Main line — SalesSales & Support IVR flow Active
+65 3159 1240Support hotlineSupport queue queue Active
+65 3159 1255Jane Tan — directext. 1010 extension Active
+65 3159 1299After-hoursmailbox: main voicemail Inactive
1
2
3
4
  1. Call Routing in the Platform group — the active view; ACD queues, DIDs, trunks and LCR all live here.
  2. Add DID — opens the create-number form (number, label, destination type and destination).
  3. Routes to — the resolved destination. Each row names the flow / queue / extension / mailbox the number hands calls to.
  4. Type pill — the destination type (flow, queue, extension, voicemail) that decides how the call is handled.
The Inbound DIDs panel, with the four destination types in use.

12.2.1Adding a DID

  1. Open Call Routing. From the sidebar choose PlatformCall Routing, then find the Inbound DIDs panel.
  2. Click Add DID. Enter the phone number in E.164 form (e.g. +6531591234) and a human label such as “Main line — Sales”.
  3. Choose the destination type. Pick flow, queue, extension or voicemail. The field below changes to match.
  4. Choose the destination. For a flow or queue, pick from the live list. For an extension or mailbox, type the identifier (e.g. 1010 or main).
  5. Leave Active on so the number routes calls immediately, then Save. Switch it to Inactive to take a number out of service without deleting it.
Warning · a DID can only point at something that exists

If a DID's destination is a flow that is not yet published, or a queue/extension that was deleted, the inbound engine answers and plays “Sorry, this menu is unavailable” (or “this number is not configured”) rather than dropping the call. Always confirm the destination is live before you point a real number at it, and re-check after deleting a queue or flow.

Tenant scoping

A tenant administrator sees and edits only their own tenant's DIDs. As a platform admin you see every DID and may assign a number to any tenant by selecting the tenant when you create it. Creating, editing and deleting numbers is an admin-gated action; reading the list is available to any authenticated user in scope.

CloudCXCloudCX Administrator & Training Manual
Ch. 12 · The Voice channel

12.3The IVR — building a call flow

An IVR flow (Interactive Voice Response) is a graph of nodes with a single start node. At call time the inbound engine walks the graph from start, executing one node at a time and following the edge each node returns. You build the graph visually in the IVR / Call Flows builder: drag a node from the palette onto the canvas, wire its outputs to other nodes, edit each node in the inspector, then Save and Publish. There are seven node types:

Table 12.2 — IVR node types and how they route
NodeWhat it doesOutputs (edges)
playPlay a prompt (TTS text or an audio file), then continue.next
menuPlay a prompt and gather DTMF digits; route on the pressed key. Barge-in friendly.one per key in options, plus invalid and timeout
hoursBranch on business hours, evaluated in a named timezone.open, closed
queueEnqueue the caller into an ACD queue for up to timeout_sec (hold music + ring).on_timeout (if no agent answers)
dialBridge the caller to a single extension/number for timeout_sec.on_no_answer
voicemailPlay a greeting and record a message into a mailbox.next (optional; else hang up)
hangupTerminate the call.— (none)

A prompt — what the caller actually hears — is one of three kinds: tts (synthesize the supplied text to speech), say (identical to tts, an explicit verb), or audio (play a pre-recorded file from a url). The builder validates that every TTS prompt has text and every audio prompt has a URL.

admin.cloudcx.app/ivr-builder.html
Add node — drag
Play
Menu
Hours
Queue
Dial
Voicemail
Hangup
VALID FLOW
7 nodes · v3
Sales & Support IVR// flow · draft
Unpublished changes SavePublish

★ Hours

start
Asia/Singapore · Mon–Fri 09:00–18:00
open → Greeting · closed → After-hours VM

Greeting

play
tts: “Welcome to Acme.”
next → Main menu

Main menu

menu
“Press 1 sales, 2 support”
1 → Sales · 2 → Support · invalid → Main menu

Sales queue

queue
queue_ref: sales · 120s
on_timeout → After-hours VM

Support queue

queue
queue_ref: support · 120s
on_timeout → After-hours VM

After-hours VM

voicemail
mailbox: acme-main
records → hang up
1
2
3
4
  1. Node palette — drag any of the seven node types onto the canvas; the footer shows the flow is valid and how many nodes it has.
  2. Unpublished changes — the draft differs from the live version. Callers keep hitting the last published flow until you publish again.
  3. Save / Publish — Save stores the draft; Publish validates the graph and makes it the live flow. Publishing re-runs validation and rejects (422) a broken graph.
  4. Start node — the entry point (marked ★). Double-click any node to make it the start. The engine begins the walk here on every call.
The visual IVR builder: palette, canvas of wired nodes, and the Save / Publish actions.

12.3.1Building and publishing a flow

  1. Open the builder. Choose VoiceIVR / Call Flows and either start a new flow or Load an existing one.
  2. Drag nodes onto the canvas. Drop a play, menu, queue and so on. Click a node to open the inspector and set its prompt, options and routing.
  3. Wire the outputs. Drag from a node's output dot to another node's top dot to connect them. A menu grows one output per key you add to options, plus the invalid and timeout fall-backs.
  4. Set the start node. Double-click the first node (often an hours or play) to mark it as Start.
  5. Save the draft. The builder validates as you go and shows a red bar listing any problem — an undefined edge target or a prompt missing its text/URL.
  6. Publish. Click Publish. CloudCX re-validates the stored graph; on success the flow is marked published and becomes the live version the next call will hit. An invalid graph is rejected with the exact reason so you can fix it.
Tip · always wire the fall-backs

Give every menu an invalid and a timeout edge, and every queue an on_timeout. Without them a caller who presses a wrong key, says nothing, or waits past the timeout simply falls off the end of the graph and is hung up on. Pointing invalid back at the same menu (to re-prompt) and timeout / on_timeout at a voicemail node is the safe default.

CloudCXCloudCX Administrator & Training Manual
Ch. 12 · The Voice channel

12.3.2How the engine walks the graph

Understanding the runtime behaviour helps you design flows that behave well under pressure:

The hours node is worth a second look because it is the most common source of “why did my after-hours routing not fire” tickets. Each range names a day (0 = Monday … 6 = Sunday), an open time and a close time as HH:MM, all evaluated in the node's timezone. Ranges may span midnight (e.g. 22:0002:00). A caller is “open” if the current time in that timezone falls inside any range; otherwise the call follows the closed edge.

Warning · set the timezone explicitly

Always set the hours node's timezone to the tenant's local zone (e.g. Asia/Singapore). If you leave it unset the node evaluates in UTC, which will open and close the line at the wrong wall-clock time for everyone but UTC callers.

12.4The ACD queue — where IVR meets the agent

A queue is an ACD waiting line for one channel. When a flow reaches a queue node (or a DID routes straight to a queue), CloudCX answers, parks the caller on hold music, and asks the ACD to pick the best available agent. It then originates a leg to that agent's softphone through the SBC edge and, the instant they answer, bridges the two legs. If the agent doesn't answer within the timeout, the engine releases them and rings the next eligible agent — up to several attempts — before giving up and following on_timeout.

How the queue chooses who rings is its strategy, combined with the skills each agent must hold:

Table 12.3 — Queue distribution strategies
StrategyPicks…Use when
longest_idlethe available agent who has waited longest since their last call.You want fair load-sharing (the default).
round_robinagents in rotation, in turn.You want a predictable, even spread.
fewest_callsthe agent who has handled the fewest calls.You want to equalise call counts over a shift.
priorityby the queue's priority ordering of members.Some agents should always be tried first.

Skills are the second filter. A queue can require one or more skills at a minimum level (e.g. “Spanish ≥ 3”), and each agent is granted skills at a proficiency level. The ACD only rings agents who hold every required skill at or above the queue's minimum, then orders the eligible pool by the strategy. The queue's stats view shows how many agents are members, how many are available right now, and which skills it requires.

Reserve to avoid double-ring

The moment an agent is chosen the ACD flips their presence to a non-routable state, so a second simultaneous call can never be handed the same agent.

Retry on no-answer

If the agent doesn't pick up in time they are returned to available and the next eligible agent is tried, up to the attempt limit.

Settle on answer

The agent who answers becomes on-call and flips back to available through the normal presence API when they finish their wrap-up.

Note · the full ACD model is Chapter 11

This chapter covers queues only as far as the voice path needs them. Creating queues, defining the skill catalogue, assigning agents and tuning routing in depth is the subject of the dedicated ACD & skills-based routing chapter. From the voice side, the key idea is simply: a queue node names a queue, and the queue's strategy + required skills decide which agent's phone rings.

CloudCXCloudCX Administrator & Training Manual
Ch. 12 · The Voice channel

12.5Outbound voice — trunks, least-cost routing & the dialer

Outbound calls leave CloudCX through a SIP carrier trunk: a gateway to a PSTN carrier, registered (or IP-authenticated) with the carrier's host. When a call is placed to a phone number, CloudCX runs least-cost routing (LCR) to pick which trunk carries it: it matches the dialled number's longest destination prefix against your LCR rules and selects the matching trunk; if no rule matches it falls back to the default outbound gateway, so a tenant with no trunks provisioned still places calls. The number is normalised to digits (a leading + is preserved) and the dial-string byondswitch/gateway/<trunk>/<number> is built for CloudCX Switch.

Outbound: agent or dialer → LCR picks a trunk → carrier → customer
Agent / Dialeroriginate
Billing gatecredit check
LCRprefix → trunk
SIP trunkcarrier
CustomerPSTN

12.5.1Registering a SIP trunk

Trunks are platform-admin objects (you decide which carriers the platform routes through). A trunk's SIP password is write-only: you supply it on create/update, CloudCX stores it encrypted at rest, and no read endpoint ever returns it — the list shows only whether a password is set.

admin.cloudcx.app
Platform
Call Routing
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Call Routing// SIP carrier trunks
PRODAO

SIP carrier trunks

+ Add trunk
Trunk name
Acme Wholesale SG
Gateway host
sbc.acme-voice.sg
Port
5060
Username
byond-sg-01
Password
•••••••• (write-only)
Transport
UDP
Max channels
60
Register with carrier
View gateway configSave trunk
1
2
3
  1. Password (write-only) — accepted on save and stored encrypted; never shown again. The list only reports whether a password is set.
  2. Register with carrier — toggle on for register-based trunks; leave off for IP-authenticated gateways.
  3. View gateway config — renders the CloudCX Switch/CloudCX SBC gateway text for review. CloudCX never reloads the live SBC for you; an operator applies it by hand.
The SIP carrier trunk form, with the write-only password and the config-review action.
  1. Add the trunk. In Call Routing → SIP carrier trunks click Add trunk and enter the carrier's gateway host, port, username and (if register-based) password, the transport and a channel cap.
  2. Save it. The password is encrypted at rest. Toggle Enabled off to keep a trunk on file without routing through it.
  3. Review the gateway config. Use View gateway config to get the generated CloudCX Switch + CloudCX SBC text, then apply it on the SBC. This endpoint is review-only by design.
  4. Add LCR rules. In Least-cost routing create a rule per destination prefix (e.g. +65 → the SG trunk, '' as the catch-all), give it a priority and an optional per-minute cost. Rules are matched most-specific (longest prefix) first.
Danger · trunk config does not touch the live SBC

Saving a trunk or generating its config never writes to or reloads the running CloudCX Switch/CloudCX SBC configuration. The generated text is for an operator to install on the SBC deliberately. Do not assume a newly saved trunk carries traffic until its gateway has actually been applied and is registered on the edge.

Note · the credit gate

Every outbound originate passes a billing gate first. A prepaid tenant whose balance is exhausted, or a postpaid tenant past an explicit credit limit, is blocked with a clear “Call blocked” message (HTTP 402). A default postpaid tenant with no limit passes straight through. The gate fails open on any internal error, so it can never accidentally stop all calling.

CloudCXCloudCX Administrator & Training Manual
Ch. 12 · The Voice channel

12.5.2The outbound campaign dialer (agent desktop)

Agents place campaign calls from a dedicated Campaign work-mode tab in the agent desktop, kept clearly separate from their inbound inbox. The agent picks a campaign, and the dialer behaves according to the campaign's dial mode: in preview mode the agent sees the next contact and presses Dial when ready; in progressive/predictive modes the server auto-dials and the agent simply works the connected contact. Either way, after the call the agent records a disposition — an outcome code, free-text and optional CRM notes — and may schedule a callback.

cloudcx.app/agent
Work mode
Inbox
Campaign
AVAILABLE
ext 1010 · SIP up
Campaign// Q3 Renewals · preview mode
preview modeJT
Campaign
Q3 Renewals · preview · running

Preview · next lead

ready
MKMaria Kohattempt 1+65 9123 4567Skip Dial now

Disposition

after the call
Outcome code
salecallbackno interestvoicemaildo not call
Disposition
Interested — sending quote
Schedule a callback
SkipSave & next →
1
2
3
4
  1. Campaign work-mode — a separate tab from the inbox. Switching to it loads the agent's campaigns and shows the dial-mode pill.
  2. Dial now — in preview mode this originates the customer leg and bridges it to the agent's softphone; Skip moves to the next lead without calling.
  3. Outcome code — a single-select disposition code (sale, callback, no interest, voicemail, do-not-call, …) that pre-fills the free-text field.
  4. Save & next — records the disposition (and an optional callback time), then pulls the next contact.
The agent's outbound campaign dialer in preview mode, with the disposition form.
Tip · DNC is enforced for you

If a contact is on the Do-Not-Call list (or otherwise not dialable), the dial is refused and the dialer simply advances to the next lead — the agent never has to check a list manually. Campaign creation, contact lists and dial-mode configuration are covered in the Outbound campaigns chapter.

CloudCXCloudCX Administrator & Training Manual
Ch. 12 · The Voice channel

12.6Call recording & transcription

CloudCX can record calls and, when speech-to-text is configured, transcribe them automatically for AI insights, QA scoring and search. Recording is governed at the platform level by a “Recording on by default” switch and a recording retention window (in days) under GovernanceSettings; new tenants then record calls unless overridden.

When a call ends, the engine writes a CDR that includes the path of any recording it captured. That same event fires a best-effort transcription: a background job runs speech-to-text over the recording and stores the transcript for the call. The job is fully self-gating — if STT is not configured it is a clean no-op — and it never blocks or delays the call itself. Voicemail messages are recorded and transcribed the same way.

Call endsCloudCX Switch emits the hangup event with timings, hangup cause and the recording path.CHANNEL_HANGUP
CDR persistedAn immutable call-detail record is written, attributed to the tenant, reseller and (for an agent leg) the agent.Cdr row
Transcription queuedA background STT job is spawned for the recording — best-effort, self-gating, never blocking.async task
Insights & QAThe transcript feeds AI summaries, sentiment, QA scorecards and search.downstream

A CDR is the durable record auditors and analysts rely on. Each row carries the call's unique id, caller and destination numbers, direction, start / answer / end timestamps, the total duration and the billable seconds (billsec), the hangup cause, the codecs used and the recording path. CDRs are tenant-scoped: a tenant sees only their own calls; a platform admin sees all.

Table 12.4 — Key fields on a call detail record (CDR)
FieldMeaning
call_uuidThe unique id of the call leg (stable across the platform).
directioninbound or outbound.
answer_stampWhen the call was answered (empty for an unanswered call).
billsecBillable seconds — the answered talk time used for rating.
hangup_causeWhy the call ended (e.g. NORMAL_CLEARING, NO_ANSWER, USER_BUSY).
recording_pathWhere the audio was filed, if recording was on. Drives transcription & playback.
Warning · recording is a compliance decision

Recording calls (and storing transcripts) carries legal obligations that vary by jurisdiction — consent announcements, retention limits and data-subject rights. Set the retention window to your policy, make sure your IVR plays any required “this call may be recorded” notice (a play node at the top of the flow), and confirm with your compliance team before enabling recording for a new tenant.

12.7The agent experience — screen-pop & the softphone

When the ACD rings an agent, CloudCX pushes an incoming-call screen-pop to that agent's desktop at the same moment their softphone starts ringing. The pop shows the caller number, the matched contact (looked up by phone number in the CRM, if found) and the queue the call came from, with Accept and Decline buttons. Accepting answers the call on the softphone and brings up the voice workspace; declining hangs up the ringing leg. If the call is answered elsewhere or times out, the pop clears itself.

cloudcx.app/agent

Incoming call

Sales
MK
Maria Koh
+65 9123 4567
★ matched · Maria Koh — Acme Pte Ltd
Ringing your softphone now…
DeclineAccept
1
2
  1. Caller & matched contact — the screen-pop shows the number plus, when the CRM matches it, the contact's name and company, and the queue (here, Sales) as a badge.
  2. Accept / Decline — Accept answers on the softphone and opens the voice workspace; Decline hangs up the ringing leg. The pop auto-clears if the call is answered elsewhere or times out.
The inbound screen-pop the agent sees as their softphone rings.

The softphone itself gives the agent full in-call control: Hold (parks the customer on hold music), Mute, Transfer (warm or blind, to another agent or extension), Conference, and a keypad for sending DTMF (useful when navigating an external IVR). These are standard call controls and behave as you would expect; the screen-pop is the CloudCX-specific addition that gives them context before they answer.

Note · the phone is the source of truth

The screen-pop is a convenience layered over the call, not the call itself. If the pop ever fails to appear (for example during a transient connection blip) the softphone still rings and the agent can still answer — no call is ever lost because a pop was missed. Supervisors can also listen in on live calls from the supervisor console; live-call monitoring is covered in the supervision chapter.

CloudCXCloudCX Administrator & Training Manual
Ch. 12 · The Voice channel

12.8Worked example — standing up a sales & support line

Let us put the whole chapter together. Acme Pte Ltd has bought the number +65 3159 1234 and wants: during business hours, a menu offering 1 for Sales and 2 for Support, each routed to its own queue; outside hours (and when no agent answers within two minutes), a voicemail. Here is the end-to-end build.

  1. Create the two queues. In Call Routing → ACD queues add Sales and Support, both with the longest_idle strategy and channel voice. Add the relevant agents as members, and (optionally) require a “Sales” / “Support” skill on each.
  2. Build the flow. In Voice → IVR / Call Flows create “Sales & Support IVR” with these nodes:
    • an hours start node (Asia/Singapore, Mon–Fri 09:00–18:00) → open to the greeting, closed to the voicemail;
    • a play greeting (“Welcome to Acme. This call may be recorded.”) → the menu;
    • a menu (“Press 1 for sales, 2 for support”) with 1→Sales queue, 2→Support queue, invalid→re-prompt the menu, timeout→voicemail;
    • two queue nodes (sales, support, 120s) each with on_timeout→voicemail;
    • a voicemail node (mailbox acme-main).
  3. Validate and publish. Save; clear any red-bar errors (a dangling edge or an empty prompt); then Publish. The flow is now the live version.
  4. Point the DID at it. In Inbound DIDs add +6531591234, label it “Main line — Sales”, set destination type flow and pick the published flow. Leave Active on.
  5. Confirm recording. Check that “Recording on by default” matches Acme's policy and that the greeting carries the recording notice you added in step 2.
  6. Test the path. Call the number during hours: you should hear the greeting, press 1, hear hold music, and watch an available Sales agent's softphone ring with a screen-pop. Hang up and confirm a CDR appears with the right direction, duration and recording path.

What happens at runtime, in order: the carrier delivers the call to the SBC; the DID resolves to the flow; the engine answers and runs hours → (open) playmenu; the caller presses 1; the queue node parks them on hold music and asks the ACD for the longest-idle available Sales agent; that agent's phone rings with a screen-pop; on Accept the legs bridge; on hang-up a CDR is written and (recording on) the audio is filed and queued for transcription. If no agent answered within 120s, the call would have followed on_timeout to the voicemail node instead.

Try it — add an after-hours & overflow path

Using a test tenant, extend the worked-example flow so that callers are never simply hung up on:

  1. Open the “Sales & Support IVR” flow in the builder.
  2. Confirm the hours node's timezone is your tenant's local zone, then temporarily set the open window to a time that is not now, so the closed branch fires.
  3. Make sure both queue nodes' on_timeout and the menu's timeout all point at the voicemail node.
  4. Add a play node before the voicemail that says “Our office is closed; please leave a message”, wired into the voicemail node.
  5. Save and Publish. Call the number: you should reach the closed-hours voicemail. Then restore the real hours, publish again, and confirm the daytime menu returns.
  6. Bonus: add a third menu option 0 that dials the reception extension, with on_no_answer falling back to the voicemail.

12.9Troubleshooting quick-reference

Table 12.5 — Common voice symptoms and where to look
SymptomLikely cause & fix
Caller hears “this menu is unavailable”The DID points at a flow that was never published (or was deleted). Publish the flow or re-point the DID.
After-hours routing fires at the wrong timeThe hours node's timezone is unset (defaults to UTC). Set it to the tenant's local zone.
Caller waits on hold then gets voicemailNo eligible agent was available within the queue timeout. Check queue membership, required skills and live presence in the queue stats.
Outbound call blocked with HTTP 402Prepaid balance exhausted or postpaid credit limit reached. Top up or raise the limit (Revenue → Billing).
Outbound call fails on a new trunkThe generated gateway config was never applied to the SBC, or the carrier rejected registration. Re-apply the config; verify the trunk registers.
No recording / transcript on a callRecording is off for the tenant, or STT is not configured. Toggle recording; transcription is a no-op without STT.
DIDDirect Inward Dialing
An inbound phone number the tenant owns, mapped to a routing destination (flow, queue, extension or voicemail).
IVRInteractive Voice Response
The node graph that greets and triages an inbound caller before (optionally) routing them to a queue.
ACDAutomatic Call Distribution
The engine that picks the best available agent for a queue using its strategy and required skills.
LCRLeast-Cost Routing
Per-prefix rules that choose which carrier trunk carries an outbound call, falling back to a default gateway.
CDRCall Detail Record
The immutable per-call record (timings, direction, hangup cause, billsec, recording path) used for billing, analytics and QA.
SBCSession Border Controller
The CloudCX SBC edge + CloudCX Switch media engine that fronts the PSTN and routes SIP to and from agents and carriers.
Bridge
Connecting two call legs (caller and agent) into one conversation once the agent answers.
Screen-pop
The card pushed to the agent's desktop as their phone rings, showing caller number, matched contact and queue.
What's next

You now have the full voice path from number to agent and back. The neighbouring chapters go deeper on the pieces this one touched: ACD & skills-based routing for queue and skill design, Outbound campaigns for dialer modes and contact lists, Supervision & quality for live monitoring and QA scorecards, and AI insights for what the transcripts unlock.

CloudCX · Administrator & Training ManualCh. 12 · The Voice channel
CloudCXCloudCX Administrator & Training Manual
Ch. 13 · Web chat
Chapter 13

Web chat and the embeddable widget

Web chat is the first — and simplest — of the CloudCX digital channels. A single <script> tag drops a branded chat bubble onto any website; when a visitor opens it, CloudCX starts an omnichannel conversation, routes it to an available agent through the ACD, and streams the exchange live over a WebSocket. This chapter shows you how the widget works end-to-end, how to customise and embed it, how chats are routed to agents, and how the optional AI auto-responder can field the first messages. It closes with a fully worked embed and a Try-it exercise.

Where you do this work

You enable the channel platform-wide under PlatformChannels at admin.cloudcx.app; you tune the AI deflection bot under GovernanceAI Assistant. The widget asset itself (widget.js) is served from your static host/CDN, and the embed snippet lives on the customer’s own website. Agents handle the resulting chats from the Agent Desktop inbox (Chapter 14).

13.1How web chat fits together

Web chat is “omnichannel” by design: a chat is just an OmniThread whose channel is webchat, and each turn is an OmniMessage with a direction of inbound (from the visitor) or outbound (from the agent or the AI). The very same tables and routing power WhatsApp, SMS and email later — only the transport differs. Three moving parts make a live chat:

The web-chat request path, from website visitor to CloudCX agent.
Websitewidget.js bubble
Start threadPOST /chat/threads
ACD routewebchat queue
Agent Desktopclaim & reply
WebSocket/chat/ws/<thread>
Live streamboth directions
PersistedOmniThread + OmniMessage
  1. The bubble. widget.js injects a floating chat bubble (bottom-right) and all of its own CSS — it has zero dependencies and touches none of the host page’s styles.
  2. Start a thread. On the visitor’s first open, the widget POSTs /api/v1/chat/threads with { channel: "webchat", tenant_id }. CloudCX creates the thread and routes it (§13.5).
  3. Open the socket. Using the returned thread id, the widget opens a WebSocket to /api/v1/chat/ws/<thread_id>wss:// on HTTPS sites, ws:// on HTTP — and shows “Connected. How can we help?”
  4. Exchange turns. Visitor messages travel over the socket as inbound; the agent (or AI bot) replies outbound. Every message is persisted and broadcast to all connected sockets on the thread, so the visitor and the agent see each other in real time.
Tip · One channel, many surfaces

Because chat threads are channel-agnostic, everything you learn here — routing through the ACD, claiming, the AI deflection bot, the inbox — carries straight over to WhatsApp, SMS and email. Web chat is the easiest place to learn the omnichannel model before you wire up provider credentials for the other channels.

13.2Enabling the channel

Before the widget can do anything useful, Web chat must be switched on for the platform. Open PlatformChannels. Each omnichannel surface has a master switch in the Platform channel availability panel, and a card describing what it does. Web chat needs no shared provider credentials (it has no external carrier), so flipping the switch is all that is required.

admin.cloudcx.app
Platform
Channels6
Credentials
Governance
AI Assistant
Security
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Channels// 05 — omnichannel
PRODAO
Omnichannel

Channels

Surfaces the platform supports and which shared credentials are configured.

Platform channel availability

master switches
Voice SBC
Web chat embeddable widget
WhatsApp needs credentials
SMS needs credentials

Web chat

Enabled
Live chat widget for any site, with bot deflection & co-browse. No shared provider credentials required. Resellers may brand and embed it on their own customers’ sites.
1
2
3
4
  1. Channels — the Platform group’s omnichannel view; the active section in the sidebar.
  2. Web chat row — one master switch per surface. The pill notes there is no external carrier.
  3. Master switch (on) — toggling this makes the channel available to tenants and resellers.
  4. Channel card — a per-channel description; web chat shows Enabled / no credentials required.
The Channels view with Web chat enabled and no provider credentials needed.
  1. Open Channels. Go to PlatformChannels.
  2. Toggle Web chat on in the Platform channel availability panel. It turns the card’s status pill to Enabled.
  3. Confirm no credentials are flagged. Unlike WhatsApp or SMS, the Web chat card never asks for a provider key — the browser talks straight to the CloudCX API.
Warning · Enabling the channel is not the same as embedding it

Switching Web chat on at the platform makes the channel and its API available; it does not place the bubble on anyone’s site. The widget appears only where the embed snippet (§13.4) is installed. Likewise, disabling the channel here is the platform-wide kill switch — do it only during a planned change window.

CloudCX · Administrator & Training ManualCh. 13 · Web chat
CloudCXCloudCX Administrator & Training Manual
Ch. 13 · Web chat

13.3Anatomy of the chat widget

The visitor-facing widget is intentionally small: a circular bubble that, when clicked, opens a 340×460 pixel panel with a branded header, a scrolling message area, and a single-line composer. It carries the CloudCX magenta-to-orange gradient out of the box and renders every message body with textContent, so visitor or agent text is never interpreted as HTML — it is XSS-safe by construction.

acme-co.example
Acme Telecom
A customer’s website. The CloudCX bubble floats above the page, bottom-right, on top of everything else.
Chat with Support×
Connected. How can we help?
Hi! I was double-charged this month.
Priya · Support
Happy to help — let me check.
Type a message…
1
2
3
4
5
  1. Launcher bubble — a 60 px gradient circle, fixed bottom-right (z-index very high so it floats above page content). Click toggles the panel.
  2. Header — the gradient title bar; its text comes from the title option (default “Chat with us”). The × closes the panel.
  3. System line — centred status notes: Connected…, Disconnected, or an error such as Couldn’t connect.
  4. Visitor message — an inbound bubble, left-aligned, light background.
  5. Agent / AI message — an outbound bubble, right-aligned, gradient, with the sender’s name above it.
The widget on a customer site: bubble (closed) and the open conversation panel.

13.3.1The widget’s lifecycle in one glance

Internally the widget keeps a tiny state machine: it is idle until the visitor first opens the panel, at which point it starts a thread and then connects the socket. Subsequent opens reuse the same thread and socket. If the socket is not yet ready when the visitor hits send, the widget transparently falls back to a REST POST so no message is lost.

Launcher size
60 px circle
Panel
340 × 460 px (responsive)
Dependencies
None — vanilla JS
Styling
Self-injected, scoped
Transport
WebSocket, REST fallback
Visitor identity
Anonymous (thread id = capability)
Note · Visitors are anonymous in this release

The widget does not ask the visitor to log in. The thread id returned by POST /chat/threads is the capability used to open the socket and post messages. This is perfect for a public “contact us” chat. If you need to bind a chat to a known, authenticated customer, pass identifying details (see visitor_name / visitor_contact in §13.6) and treat the thread id as a short-lived secret on the page.

CloudCX · Administrator & Training ManualCh. 13 · Web chat
CloudCXCloudCX Administrator & Training Manual
Ch. 13 · Web chat

13.4Customising and embedding the widget

You install the widget by adding two lines of HTML just before </body> on the customer’s site. The widget reads its configuration from a global window.CloudCX Chat object set before the script, or from data-* attributes on the <script> tag — whichever you prefer. When both are present, the data-* attributes win, which is handy for per-page overrides.

13.4.1Configuration options

API originapiBase
data-api-base
The origin of your CloudCX API — scheme + host, with no trailing /api/v1 (the widget appends that itself). Defaults to the page’s own origin. The WebSocket scheme is derived automatically: https://wss://, http://ws://.
TenanttenantId
data-tenant-id
Optional tenant UUID the thread should be routed to. Omit it (or leave it null) for an anonymous, platform-level thread that only platform admins can see. Set it so the chat lands in the correct tenant’s inbox — this is what makes the widget multi-tenant.
Titletitle
data-title
The header text of the chat panel. Defaults to “Chat with us”. Use it to match the customer’s brand, e.g. “Chat with Acme Support”.
Table 13.1 — Widget configuration keys (global object and the matching data-attribute)
Global keyData-attributeDefaultMeaning
apiBasedata-api-basepage originAPI origin; /api/v1 appended automatically.
tenantIddata-tenant-idnullTenant UUID to route the thread to.
titledata-titleChat with usHeader text of the chat panel.
Tip · The single most important field

Almost every misrouted-chat support ticket comes down to a missing or wrong tenantId. Always set it to the UUID of the tenant whose agents should answer. You will find that UUID under TenancyTenants — open the tenant and copy its ID from the detail header.

13.4.2The embed snippet, step by step

  1. Host widget.js. Publish the widget asset on a static host or CDN you control, e.g. https://cdn.yourdomain.com/widget.js. CloudCX ships the file under web/widget/.
  2. Find the API origin. This is your CloudCX API host — for example https://api.yourdomain.comwithout a trailing /api/v1.
  3. Copy the tenant UUID from TenancyTenants for the tenant whose agents will answer.
  4. Paste the snippet just before </body> on every page that should show the bubble, filling in the three values.
  5. Reload the page. The gradient bubble appears bottom-right. Click it: you should see Connected. How can we help? within a moment.
Embed — global-object form (set config before the script)
<!-- CloudCX Chat — paste just before </body> -->
<script>
  window.CloudCX Chat = {
    apiBase:  "https://api.yourdomain.com",   // API origin — no /api/v1
    tenantId: "8f3c1e0a-2b44-4c7d-9a10-7e2f0b9c5d31", // tenant UUID
    title:    "Chat with Acme Support"          // header text
  };
</script>
<script src="https://cdn.yourdomain.com/widget.js"></script>
Embed — data-attribute form (one self-contained tag; attributes override the global)
<script
  src="https://cdn.yourdomain.com/widget.js"
  data-api-base="https://api.yourdomain.com"
  data-tenant-id="8f3c1e0a-2b44-4c7d-9a10-7e2f0b9c5d31"
  data-title="Chat with Acme Support"></script>
Warning · CORS and mixed content

The visitor’s browser calls your API cross-origin (the site and the API are on different hosts), so the API’s cors_origins must allow the site’s origin. The prototype defaults to ["*"]; for production, restrict it to the real customer domains. Also serve both the site and the API over HTTPS — a secure page cannot open an insecure ws:// socket, which would silently break the live stream.

CloudCX · Administrator & Training ManualCh. 13 · Web chat
CloudCXCloudCX Administrator & Training Manual
Ch. 13 · Web chat

13.5Routing chats to agents

When a chat thread is created, CloudCX tries to put it in front of a live agent automatically. It uses the same ACD machinery as voice (Chapter 10): the thread is routed through the default web-chat queue, applying that queue’s skills-based distribution strategy. The result is one of two outcomes:

Auto-assigned

If the ACD finds an eligible, available agent, the thread is assigned to them and its status becomes assigned. It appears in that agent’s inbox immediately.

Left open

If no agent is eligible or available, the thread stays open for any qualified agent in the tenant to claim from the inbox.

The selection itself happens in two tiers, and — critically — routing never raises: it sits on the visitor’s request path, so any hiccup (a cold cache, no agents online) degrades gracefully to “leave it open” rather than failing the chat.

Two-tier selection when a web-chat thread is created.
1 · ACD queue
Skills-based pick from the default webchat queue’s available members.
primary
2 · Presence
If the queue yields nobody, fall back to the simple presence picker (longest-idle available agent).
fallback
Result
An agent ⇒ assigned; nobody available ⇒ open for claim.
outcome

13.5.1The agent’s side

Agents work chats from the omnichannel inbox on the Agent Desktop. The inbox polls for active threads (those whose status is open or assigned) and filters by channel. Selecting a chat claims it (assigning it to the agent), loads the message history, and opens the live socket so new visitor turns stream in. The agent types a reply (sent outbound), and closes the conversation when it is resolved.

cloudcx.app/agent
Inbox
All ChatWhatsAppSMS
Visitor open
Hi! I was double-charged this month.
Visitor assigned
Can I change my plan?
Web chat// thread · assigned to you
Connected Close chatPS
Hi! I was double-charged this month.
Sorry about that — checking your last invoice now.
Type your reply… Send
1
2
3
4
5
  1. Channel filtersAll / Chat / WhatsApp / SMS; the same inbox handles every digital channel.
  2. Thread list — active conversations, newest activity first, each with a status pill (open / assigned).
  3. Connected — the live socket is open; new visitor turns arrive instantly.
  4. Close chat — marks the thread closed and removes it from the active inbox.
  5. Reply composer — sends the agent’s message outbound; it streams to the visitor’s widget.
The omnichannel inbox handling a web chat: select to claim, reply, then close.
Note · Tenant isolation

An agent only ever sees live threads for their own tenant. A thread created with no tenant_id (anonymous) is visible only to platform admins — another reason to always set tenantId in the embed so chats reach the right team.

CloudCX · Administrator & Training ManualCh. 13 · Web chat
CloudCXCloudCX Administrator & Training Manual
Ch. 13 · Web chat

13.6AI deflection: answering before an agent

Web chat has an optional, opt-in AI auto-responder. When you enable it for a tenant and name the webchat channel, CloudCX leaves a new chat open (it skips the human pre-assignment) so the bot can field the visitor’s first message. The bot replies as “AI Assistant” and either resolves the question or hands off to a human — routing the thread through the very same ACD queue and posting a warm holding message so the visitor is never left silent.

You configure it under GovernanceAI Assistant. Every setting maps to one field on the tenant’s bot configuration:

admin.cloudcx.app
Governance
AI Assistant
Security
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
AI Assistant// auto-responder · opt-in
PRODAO
Governance

AI Assistant

Save
Enable auto-responder
Channels
webchatwhatsappsmsemail
Persona / system prompt You are Acme Telecom’s friendly support assistant. Be concise. Hand off billing changes to a human.
Max bot turns5
AI status AI configured
Hand-off message Thanks for your patience — I’m connecting you with a member of our team…
1
2
3
4
5
  1. AI Assistant — the Governance config screen, backed by the per-tenant bot configuration.
  2. Enable — the opt-in master switch (enabled); off by default, so behaviour is unchanged until you turn it on.
  3. Channels — which channels the bot handles (channels); webchat is selected here.
  4. Persona — the system prompt (persona): tone, scope and escalation rules prepended to the model.
  5. Max turns — how many bot replies a single thread gets before it auto-hands to a human (max_turns, default 5).
The AI Assistant config: enable, pick channels, set the persona, cap turns, define hand-off.
Warning · An enabled bot needs the shared AI key

The auto-responder can only reply when the platform’s shared AI credentials are configured. The screen shows an AI configured badge when they are; if it reads not configured, an enabled bot simply won’t answer — chats fall through to normal human routing until you add the key under PlatformCredentials.

The bot is engineered to be invisible when it should be: if it is disabled, the channel isn’t listed, the thread already belongs to a human, the turn budget is spent, AI isn’t configured, or anything goes wrong, it changes nothing and the chat is routed to a person exactly as in §13.5. An agent reply on a bot-handled thread naturally ends the bot’s involvement.

CloudCX · Administrator & Training ManualCh. 13 · Web chat
CloudCXCloudCX Administrator & Training Manual
Ch. 13 · Web chat

13.7Worked example: put chat on Acme Telecom’s site

Acme Telecom (a tenant on the platform) wants a “Chat with Support” bubble on its marketing site acme.example. Its agents will answer from the Agent Desktop, and Acme wants the AI assistant to handle simple questions first, handing billing issues to a person. Here is the end-to-end build.

  1. Confirm the channel is on. Under PlatformChannels, verify Web chat shows Enabled (§13.2).
  2. Get Acme’s tenant UUID. Open TenancyTenants, click Acme Telecom, and copy its ID — say 8f3c1e0a-2b44-4c7d-9a10-7e2f0b9c5d31.
  3. Staff the web-chat queue. In VoiceCall Routing (the same ACD that serves chat), make sure Acme’s default webchat queue has members and they are set available (Chapter 10).
  4. Turn on AI deflection. Under GovernanceAI Assistant, enable the bot for Acme, tick webchat, set the persona, leave Max turns at 5, and confirm the AI configured badge (§13.6). Save.
  5. Embed the snippet. Add the two lines below just before </body> on every Acme page. Reload and click the bubble.
acme.example — paste before </body>
<script>
  window.CloudCX Chat = {
    apiBase:  "https://api.cloudcx.app",
    tenantId: "8f3c1e0a-2b44-4c7d-9a10-7e2f0b9c5d31",  // Acme Telecom
    title:    "Chat with Acme Support"
  };
</script>
<script src="https://cdn.cloudcx.app/widget.js"></script>

13.7.1What happens when a visitor chats

The Acme chat, from bubble click to resolution.
Visitor opens
Widget POSTs /chat/threads with Acme’s tenant_id; socket opens; “Connected.”
start
AI fields it
Bot is enabled for webchat ⇒ thread left open; AI Assistant answers the first question.
deflect
“I was double-charged”
A billing issue ⇒ bot sets handoff: routes to Acme’s web-chat queue and posts the holding message.
handoff
Priya claims
An available Acme agent is auto-assigned (status assigned); she sees the full transcript and replies.
human
Resolved
Priya clicks Close chat; the thread is marked closed and leaves the active inbox.
close

13.7.2Verifying it works

Try it · Embed and route your first chat

Goal: stand up a working web chat for a test tenant and watch a message flow end-to-end.

  1. In Channels, confirm Web chat is enabled.
  2. Create or pick a test tenant and copy its UUID from Tenancy › Tenants.
  3. Add at least one agent to that tenant’s webchat queue and set yourself available.
  4. Open the shipped web/widget/demo.html, set apiBase to your API origin and tenantId to your test tenant, and load the page.
  5. Click the bubble and send “Hi”. In the Agent Desktop inbox, find the chat under Chat, claim it, and reply.
  6. Check: your reply appears in the widget instantly, and the inbox status moves open → assigned.

Stretch: enable the AI Assistant for webchat, set Max turns to 1, and watch the bot answer once, then hand off to you on the second message.

CloudCX · Administrator & Training ManualCh. 13 · Web chat
CloudCXCloudCX Administrator & Training Manual
Ch. 13 · Web chat

13.8API and data model reference

You will rarely call these directly — the widget and the Agent Desktop do — but knowing the endpoints makes troubleshooting precise. Visitor endpoints are anonymous; agent endpoints require authentication and are scoped to the agent’s tenant.

Table 13.2 — Web-chat endpoints (all under the /api/v1 prefix)
Method & pathWhoPurpose
POST /chat/threadsVisitorStart a thread; returns its id; routes via the ACD.
WS /chat/ws/<thread_id>Visitor / AgentLive bidirectional stream of messages on the thread.
POST /chat/threads/<id>/messagesEitherPersist + broadcast a message (REST fallback to the socket).
GET /chat/threadsAgentActive (open + assigned) threads for the agent’s tenant.
GET /chat/threads/<id>/messagesAgentFull message history for one thread.
POST /chat/threads/<id>/claimAgentAssign the thread to the calling agent (status → assigned).
POST /chat/threads/<id>/closeAgentMark the thread closed.
GET / PUT /bot/configAdmin / ResellerRead or upsert the AI auto-responder configuration.

13.8.1The objects behind a chat

OmniThread conversation
One conversation on one channel. Key fields: channel (webchat), status (open / assigned / closed), tenant_id, assigned_user_id, the optional visitor_name / visitor_contact / subject, and last_message_at.
OmniMessage turn
One message in a thread: direction (inbound from the visitor, outbound from the agent or AI), sender_name, and the body. AI replies carry the sender name “AI Assistant”.
BotConfig AI
The opt-in auto-responder, one per tenant (or a platform-wide row): enabled, channels, persona, max_turns and handoff_message.

13.9Troubleshooting

Table 13.3 — Common web-chat issues and where to look
SymptomLikely causeFix
Bubble never appearsSnippet missing, blocked, or widget.js 404Confirm both <script> lines are before </body> and the asset URL loads.
“Couldn’t connect”Wrong apiBase, or CORS blocks the originSet apiBase to the API origin (no /api/v1); allow the site origin in cors_origins.
Connects but no live updatesMixed content: HTTPS page, ws:// socketServe the API over HTTPS so the widget uses wss://.
Chat never reaches an agentNo tenantId, or empty/idle queueSet tenantId; staff the webchat queue and set agents available (§13.5).
Lands in the wrong inboxWrong tenantId in the embedCopy the exact UUID from Tenancy › Tenants.
AI never repliesBot off, channel not listed, or AI key missingEnable the bot for webchat; confirm AI configured (§13.6).
Danger · Treat the thread id as a secret on the page

Because visitors are anonymous, the thread id is the only thing needed to read and post to a chat. Do not log it, expose it in shareable URLs, or embed it server-side in cached pages. For higher-assurance deployments, front the chat with a signed, short-lived visitor token rather than the raw thread id.

13.9.1Chapter recap

Note · Where to go next

The chat machinery here is shared by every digital channel. Chapter 14 — WhatsApp, SMS, email and social reuses these exact threads and queues, adding the carrier credentials and webhooks. The agent experience — the inbox, claiming, replies and wrap-up — is covered in the Agent Desktop chapter, and live agent presence that drives routing is in Chapter 16 — Supervision & quality.

CloudCX · Administrator & Training ManualCh. 13 · Web chat
CloudCXCloudCX Administrator & Training Manual
Ch. 14 · WhatsApp, SMS & Email
Chapter 14

WhatsApp, SMS and email channels

Voice and web chat are not the whole story. CloudCX is an omnichannel platform, and three more conversational surfaces — WhatsApp (the Meta Cloud API), SMS (Twilio), and email (any IMAP/SMTP mailbox) — all land in the same agent desktop, routed by the same ACD, threaded by the same OmniThread model as a chat. This chapter is the administrator’s guide to turning each of them on: where the credentials live (the encrypted vault under Platform Credentials), how the inbound webhooks and pollers work, how an agent’s reply goes back out, and a fully worked, step-by-step setup for all three. By the end you will be able to connect a brand-new tenant’s WhatsApp number, SMS line and shared mailbox from a cold start.

One desktop, one routing engine, three transports

WhatsApp, SMS and email each have their own transport adapter (how a message is parsed inbound and pushed outbound), but everything above the transport is shared: an inbound message becomes an OmniThread + OmniMessage, is offered to the optional AI bot, then routed through the ACD’s default per-channel queue (with a presence-picker fallback) and broadcast live to the agent over the same WebSocket as web chat. An agent therefore handles a WhatsApp, an SMS and an email exactly the way they handle a chat — one inbox, one reply box.

14.1 The shape of a channel

Before the individual providers, it helps to hold the same mental model for all three. Every channel is made of four parts, and you configure them in this order:

Credentials
in the vault
Inbound
webhook / poller
Route
ACD → agent
Reply
agent → out
The four moving parts of every digital channel — configured left to right.
Table 14.1 — The three digital channels at a glance
ChannelProviderInboundOutboundReseller BYO?
WhatsAppMeta Cloud API (Graph v19.0)Webhook POST /webhooks/whatsappGraph /{phone_id}/messages Yes
SMSTwilio Programmable MessagingWebhook POST /webhooks/smsTwilio REST Messages.json Yes
EmailAny IMAP + SMTP mailboxBackground IMAP poll (30 s)SMTP submission (587 / 465) Platform only
Tip · Shared creds vs. bring-your-own

WhatsApp and SMS support a bring-your-own (BYO) model: a white-label reseller you have entitled can store its own Twilio / Meta keys, so its tenants send from the reseller’s own numbers and bills land on the reseller’s own provider account. When a reseller is not entitled (or hasn’t configured BYO) its tenants bind to CloudCX — they fall back to the shared platform credentials you set here. Email is platform-only — there is no per-reseller email BYO (the inbound poller is one shared mailbox). Reseller entitlement toggles are covered in the Resellers chapter; this chapter is the platform-credential side.

CloudCX · Administrator & Training ManualCh. 14 · WhatsApp, SMS & Email
CloudCXCloudCX Administrator & Training Manual
Ch. 14 · WhatsApp, SMS & Email

14.2 The credential vault — Platform Credentials

All shared connectivity keys live in one place: the Platform Credentials view in the dark sidebar (it sits in the Platform group). Each provider is a card with the exact fields that provider needs, a Save credentials / Remove footer, and a status badge — Configured ✓ when a row exists, Not set when it does not. The card list is driven by the server: GET /api/v1/admin/platform/credentials returns, per provider, whether it is configured and the ordered fields it expects (names only — secret values are never sent back).

admin.cloudcx.app
Operate
Dashboard
Call Routing
Platform
Channels6
Platform Credentials
Security
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Platform Credentials// shared provider & AI keys · encrypted
PRODAO
// connectivity & AI keys

Platform Credentials

CloudCX’s shared connectivity credentials, used for tenants that don’t bring their own. Stored encrypted.

Refresh
W

WhatsApp // Meta Cloud API

Configured ✓
Access token•••••••••••• (kept)
Phone number ID109738561234567
Verify token•••••••• (kept)
App secret•••••••• (kept)
Secrets are never shown. Leave a field blank to keep its current value.
Save credentialsRemove
S

SMS // outbound / 2-way text

Not set
Account SIDAC…
Auth token••••••••
From number+15551234567
Secrets are never shown. Leave a field blank to keep its current value.
Save credentialsRemove
1
2
3
4
5
6
  1. Platform Credentials nav item — the active view in the Platform group. Channels (the per-channel master switches, § 14.6) sits just above it.
  2. WhatsApp card Configured ✓: a credential row exists. Secret fields show a kept-value placeholder, not the real value.
  3. SMS card Not set: no row yet; the Remove button is disabled until creds are saved.
  4. Field set — the provider’s exact fields, in the order the API advertises (here Account SID / Auth token / From number for Twilio).
  5. Save credentials — validates the fields, encrypts the payload and upserts one row for the provider (PUT /credentials/{provider}).
  6. Remove — deletes the stored row; that channel then goes idle (no-op sends) until you re-configure it.
The Platform Credentials view — a configured WhatsApp card and an empty SMS card.

14.2.1 How the vault behaves

If the master key is missing, secrets can’t be stored

If the server has no encryption key set, the credential cards refuse to save (and the read endpoint reports the store as unavailable). The banner at the top of the page will say the master key is not configured. This is a server-deploy setting — set BYOND_CREDS_KEY in the API environment and refresh; never attempt to work around it by storing secrets elsewhere.

CloudCX · Administrator & Training ManualCh. 14 · WhatsApp, SMS & Email
CloudCXCloudCX Administrator & Training Manual
Ch. 14 · WhatsApp, SMS & Email

14.3 WhatsApp — the Meta Cloud API

WhatsApp uses Meta’s WhatsApp Cloud API (the Graph API, pinned at v19.0). You configure four credential fields, point Meta’s webhook at your platform once, and from then on inbound messages flow into the ACD and agent replies push back out as Cloud API text messages.

14.3.1 The four credential fields

Access token token
The Graph API bearer token (a System User or app token). Used as Authorization: Bearer… on every outbound send. Secret.
Phone number ID phone_id
The WhatsApp Business phone-number ID to send from (a numeric id from your WhatsApp Business account — not the display phone number). Sends POST to /{phone_id}/messages.
Verify token verify_token
A shared secret you invent. Meta echoes it back during the one-time webhook verification handshake; you enter the same string in the Meta App dashboard. Secret.
App secret app_secret optional but required for inbound
The Meta App Secret. Meta signs every inbound webhook with it (X-Hub-Signature-256); CloudCX recomputes and compares the HMAC. If this is unset, inbound webhooks are rejected (fail-closed). Set it to accept inbound messages.
No App secret → no inbound

The verify token only governs the one-time GET handshake. The app secret governs every real inbound message: CloudCX verifies the X-Hub-Signature-256 HMAC over the raw request body and fails closed if the secret is unconfigured or the signature doesn’t match. A common “the handshake worked but no messages arrive” symptom is a saved verify token with a missing app secret. Set both.

14.3.2 Inbound — the webhook

Your platform exposes one public WhatsApp webhook URL. It serves two methods, both platform-level (one Meta app backs the shared webhook):

GET — verification

Meta calls GET /webhooks/whatsapp once when you subscribe, sending hub.mode, hub.verify_token, hub.challenge. CloudCX echoes the challenge back only when the mode is subscribe and the token equals your stored verify token; otherwise 403.

POST — messages

Meta delivers messages to POST /webhooks/whatsapp. CloudCX verifies the X-Hub-Signature-256 HMAC, then parses, threads, routes and broadcasts. It always returns 200 {"status":"ok"} after a valid signature, so Meta never retry-loops on a transient parse/DB hiccup.

The inbound parser walks Meta’s envelope (entry[].changes[].value.messages[]) and handles text messages, pulling the sender’s WhatsApp number, profile name, the message body and Meta’s message id (wamid). Non-text events (images, statuses, reactions) are ignored gracefully. A conversation is keyed by (channel='whatsapp', visitor_contact=<the WhatsApp number>): the first still-open/assigned thread for that number is continued, otherwise a new one opens.

14.3.3 Outbound — the agent reply

When an agent replies on a WhatsApp thread, the desktop calls POST /api/v1/channels/whatsapp/threads/{thread_id}/reply. CloudCX POSTs the text to the Cloud API /{phone_id}/messages endpoint using the resolved credentials (the reseller’s own if entitled, else the shared platform creds), then persists and broadcasts the outbound turn. If creds are unset, the channel is disabled, or the tenant is out of credit, the network send is skipped but the message is still saved and shown — the transcript and agent UI never desynchronise.

14.3.4 Connect WhatsApp — step by step

  1. Gather the four values from your Meta App / WhatsApp Business account: the access token, the phone number ID, the app secret, and a verify token you invent (any hard-to-guess string — you’ll paste the same one into Meta).
  2. Open Platform Credentials → the WhatsApp card. Paste Access token, Phone number ID, Verify token and App secret, then press Save credentials. The badge turns Configured ✓.
  3. Configure the webhook in Meta. In the Meta App dashboard’s WhatsApp → Configuration, set the Callback URL to https://cloudcx.app/api/v1/webhooks/whatsapp and the Verify token to the exact string from step 1. Press Verify and save — Meta calls your GET endpoint and the handshake succeeds.
  4. Subscribe to the messages field for the WhatsApp Business account so Meta starts delivering inbound messages to your POST webhook.
  5. Confirm the channel is enabled under Channels (it is on by default — § 14.6).
  6. Test inbound. From a phone, send a WhatsApp message to the business number. It should appear as a new thread in the agent desktop within a second or two.
  7. Test outbound. Have the assigned agent reply; confirm the text arrives on the phone.
Tip · The phone number ID is not the phone number

A frequent setup slip is pasting the display number (+65…) into Phone number ID. The field wants the numeric ID Meta assigns the number (e.g. 109738561234567), shown next to the number in the WhatsApp → API setup panel. The display number is what customers message; the ID is what you send from.

CloudCX · Administrator & Training ManualCh. 14 · WhatsApp, SMS & Email
CloudCXCloudCX Administrator & Training Manual
Ch. 14 · WhatsApp, SMS & Email

14.4 SMS — Twilio

SMS uses Twilio Programmable Messaging. It is the simplest of the three to wire: three credential fields, one webhook URL configured on the Twilio number, done. Outbound replies go via Twilio’s REST API; inbound messages arrive as a form-encoded webhook that CloudCX verifies and acknowledges with empty TwiML.

14.4.1 The three credential fields

Account SID account_sid
Your Twilio Account SID (begins AC…). The HTTP Basic auth username on the REST send, and part of the send URL (/Accounts/{SID}/Messages.json).
Auth token auth_token
Your Twilio Auth Token — the HTTP Basic password. It also keys the inbound webhook signature check (X-Twilio-Signature). Secret.
From number from_number
The Twilio number messages are sent from, in E.164 (e.g. +15551234567). This is also the number your customers text.

14.4.2 Inbound — the Twilio webhook

Twilio delivers an inbound SMS as an application/x-www-form-urlencoded POST to POST /webhooks/smsnot JSON. CloudCX reads the form (From, Body, MessageSid…), verifies Twilio’s X-Twilio-Signature against your stored auth token (fail-closed if the token is unset or the signature doesn’t match), then threads, routes and broadcasts exactly like the other channels. It always replies with empty TwiML — <Response></Response> — so Twilio treats the message as handled and never retries a poison callback.

Signatures and the public URL

Twilio signs the request over the exact URL it called. Behind a reverse proxy CloudCX reconstructs that public URL from the X-Forwarded-Proto / X-Forwarded-Host headers the proxy sets, so the HMAC matches. If you ever see signature mismatches, the usual cause is a proxy that isn’t forwarding those headers, or a Twilio webhook URL that doesn’t match your real public hostname.

14.4.3 Outbound — the agent reply

An agent reply calls POST /api/v1/channels/sms/threads/{thread_id}/reply. CloudCX delivers it through the Twilio REST API (POST /Accounts/{SID}/Messages.json with the SID/token as Basic auth and a From/To/Body form), then saves and broadcasts the turn. As with WhatsApp, an unset-creds / disabled-channel / out-of-credit case skips only the network send — the message is still recorded so the transcript stays complete.

admin.cloudcx.app
S

SMS // outbound / 2-way text

Configured ✓
Account SIDACa1b2c3d4e5f6…
Auth token•••••••••• (leave blank to keep current)
From number+6531590100
Secrets are never shown. Leave a field blank to keep its current value.
Save credentialsRemove
1
2
3
4
  1. Provider header — the SMS card title, subtitle and the Configured ✓ badge once a row exists.
  2. Account SID — rendered as a plain text field (not a secret); the Auth token below it is masked.
  3. From number — your Twilio sending number in E.164. Customers text this number.
  4. Save credentials / Remove — the same footer as every card; Remove deletes the row and idles SMS.
The SMS (Twilio) credential card — SID, auth token and the sending number.

14.4.4 Connect SMS — step by step

  1. Copy your Twilio values from the Twilio Console: the Account SID, the Auth Token, and the Phone number (E.164) you will send from.
  2. Fill the SMS card under Platform Credentials — Account SID, Auth token, From number — and press Save credentials.
  3. Set the Twilio inbound webhook. In the Twilio Console, open your number’s Messaging settings and set A message comes in to Webhook, HTTP POST, URL https://cloudcx.app/api/v1/webhooks/sms. Save.
  4. Confirm SMS is enabled under Channels (default on).
  5. Test inbound and outbound. Text the Twilio number from a phone (a thread appears in the desktop), then have the agent reply and confirm it lands on the phone.
CloudCX · Administrator & Training ManualCh. 14 · WhatsApp, SMS & Email
CloudCXCloudCX Administrator & Training Manual
Ch. 14 · WhatsApp, SMS & Email

14.5 Email — any IMAP / SMTP mailbox

Email is the one channel with no SDK and no webhook: it works with any standards-compliant mailbox over plain IMAP (inbound) and SMTP (outbound). CloudCX polls the inbox in the background, turns each new message into a routed thread, and sends agent replies back out over SMTP as Re: <subject>.

14.5.1 The seven credential fields

IMAP host imap_host
The inbound mail server, e.g. imap.example.com. This is the on/off switch: with no IMAP host the inbound poller stays idle.
IMAP port imap_port optional
Defaults to 993 (IMAPS / implicit TLS).
Username user
The mailbox login — used for both IMAP and SMTP authentication.
Password pass
The mailbox password (or an app-password where the provider requires one). Secret.
SMTP host smtp_host
The outbound mail server, e.g. smtp.example.com. With no SMTP host, agent replies are disabled (the API returns a clear “not configured” error).
SMTP port smtp_port optional
Defaults to 587 (submission + STARTTLS). Use 465 for implicit-TLS SMTPS.
From address from optional
The address replies are sent from. If omitted, the IMAP Username is used as the From address.
How transport security is chosen

Inbound IMAP always uses implicit TLS (IMAPS) on the IMAP port. Outbound SMTP picks its mode from the port: port 465 → SMTPS (implicit TLS); any other port (the default 587) → plain connect then STARTTLS upgrade. Authentication happens when a username is configured. You don’t toggle TLS anywhere — choose the right port and CloudCX does the rest.

14.5.2 Inbound — the IMAP poller

A background task polls the mailbox every 30 seconds. Each cycle it re-reads the email credentials from the vault, connects over IMAPS, fetches UNSEEN messages from INBOX, and immediately flags each one \Seen so a crash mid-cycle can’t reprocess it. Every fetched message is parsed (stdlib email — decoded subject, the plain-text body, the sender address and display name) and ingested as an inbound thread.

Poll every 30s
Fetch UNSEEN
Mark \Seen
Parse
Thread + route
The email inbound cycle — one short-lived IMAP connection every 30 seconds.

A conversation is keyed by sender: the first open/assigned email thread whose visitor_contact equals the sender address is continued, so an ongoing back-and-forth with one person stays a single thread until it is closed (mirroring how a mail client threads by sender). A brand-new sender opens a new thread seeded with their name, address and the email subject.

Tip · Live activation, no restart

The poller re-reads the vault at the top of every cycle. So the moment you press Save credentials on the Email card, polling activates on the next cycle — no service restart. Remove the IMAP host and it returns to idle just as smoothly. You can stand up the email channel on a running production system with zero downtime.

14.5.3 Outbound — the agent reply

An agent reply calls POST /api/v1/channels/email/threads/{thread_id}/reply. CloudCX sends it over SMTP to the thread’s sender with subject Re: <original subject>, then records and broadcasts the turn. Email’s reply path is stricter than WhatsApp/SMS: if SMTP isn’t configured or the channel is disabled the API returns 503 and does not persist the message (so the agent can retry once it’s set up); a hard SMTP send failure returns 502. There is no “silent no-op” for email replies.

14.5.4 Connect email — step by step

  1. Prepare a shared mailbox (e.g. [email protected]) and note its IMAP host, SMTP host, login username and password. If the provider needs an app-password (common with 2FA), generate one for CloudCX.
  2. Fill the Email card. Enter IMAP host, Username, Password and SMTP host (the required four). Set From address if it differs from the username; leave the ports blank to accept 993 / 587. Press Save credentials.
  3. Wait one cycle. Within ~30 seconds the poller activates — no restart needed.
  4. Confirm Email is enabled under Channels (default on).
  5. Test inbound. Send a message to the mailbox from any external address; a new email thread should appear in the agent desktop within ~30 seconds.
  6. Test outbound. Reply from the desktop; confirm the original sender receives a Re:… email from your From address.
The poller marks mail read

The inbound poller fetches UNSEEN messages and flags them \Seen as it ingests them. Use a dedicated mailbox for the channel — don’t point it at a human’s personal inbox, or their unread mail will be consumed and marked read. One mailbox in, agent replies out.

CloudCX · Administrator & Training ManualCh. 14 · WhatsApp, SMS & Email
CloudCXCloudCX Administrator & Training Manual
Ch. 14 · WhatsApp, SMS & Email

14.6 The Channels panel — master switches

Separate from credentials is availability. The Channels view carries a master on/off switch for every channel — Voice, Web chat, WhatsApp, SMS, Email, Social. Every switch is on by default: a channel is enabled unless an admin deliberately turns it off. The flag is purely additive, so a fresh platform behaves as if everything is on, and a database hiccup can never accidentally disable a live channel.

When you disable a channel, CloudCX does the safe thing on both sides: inbound messages are acknowledged and dropped (so providers don’t retry-loop), and outbound sends are skipped — but for WhatsApp/SMS the message is still recorded and shown to the agent. Email is stricter: a reply while email is disabled returns 503 and isn’t saved. The same view also shows the per-channel credential status as a quick “creds set / creds not set” badge, read live from the vault.

admin.cloudcx.app
Channels// omnichannel · platform toggles
PRODAO
// 05 — Omnichannel

Channels

The omnichannel surfaces the platform supports, and which shared provider credentials are configured for each.

Refresh

Platform channel availability

// live · /admin/platform/channels
WhatsApp
Two-way WhatsApp Business messaging.
SMS
Bulk and two-way text messaging.
Email
Ticketed email channel (IMAP / SMTP).

Shared provider credentials

// platform connectivity keys
WhatsApp · connected SMS · connected Email · unconfigured
1
2
3
4
  1. Platform channel availability — the master switch list, saved live to /admin/platform/channels. Flipping a switch PUTs the whole map.
  2. WhatsApp / SMS enabled — the green switch means the channel is live; default-on for every channel.
  3. Email disabled — the off switch: inbound is dropped (acked) and the agent reply path returns 503 until re-enabled.
  4. Shared provider credentials — the at-a-glance vault status mirrored here (connected / unconfigured) so you see availability and connectivity together.
The Channels panel — master availability switches above the live credential-status badges.

14.6.1 Reseller bring-your-own (BYO)

For WhatsApp and SMS, a reseller can use its own provider account instead of CloudCX’s shared one. Two things must both be true: the reseller’s entitlement toggle is on (Allow own WhatsApp / Allow own SMS, set on the reseller drawer — see the Resellers chapter), and the reseller has stored its own credentials in its portal. When both hold, that reseller’s tenants send with the reseller’s keys; otherwise they bind to CloudCX and use the shared platform creds you configured here. The resolution is per-message and silent — agents never see which credential set was used.

WhatsApp BYO
Supportedallow_own_whatsapp + reseller creds
SMS BYO
Supportedallow_own_sms + reseller creds
Email BYO
Not supported — one shared platform mailbox

14.7 Worked example — light up all three for a new tenant

A new direct tenant, Acme Retail, wants WhatsApp, SMS and an email queue, all on CloudCX’s shared infrastructure (no reseller BYO). You have Acme’s Meta and Twilio details and a dedicated mailbox. Here is the end-to-end runbook.

  1. WhatsApp creds. Platform Credentials → WhatsApp. Enter Access token, Phone number ID 109738561234567, a verify token acme-wa-7Q2x, and the App secret. Save → badge Configured ✓.
  2. WhatsApp webhook. In Meta, set Callback URL https://cloudcx.app/api/v1/webhooks/whatsapp and verify token acme-wa-7Q2x; verify, then subscribe to messages.
  3. SMS creds. SMS card → Account SID, Auth token, From number +6531590100. Save.
  4. SMS webhook. In Twilio, set the number’s inbound message webhook to POST https://cloudcx.app/api/v1/webhooks/sms.
  5. Email creds. Email card → IMAP host imap.yourco.com, Username [email protected], Password (app-password), SMTP host smtp.yourco.com, From [email protected]. Ports left blank. Save — the poller activates within 30 s.
  6. Availability. Channels → confirm WhatsApp, SMS and Email switches are all on and the credential badges read connected.
  7. End-to-end smoke test. From a test phone, WhatsApp the business number and text the Twilio number; from a test inbox, email the support mailbox. Confirm three threads land in the desktop, then have an agent reply on each and confirm all three replies arrive.
What “configured” looks like — the three credential rows the vault now holds (field names only; values are encrypted).
# GET /api/v1/admin/platform/credentials  (secret VALUES are never returned)
[
  { "provider": "whatsapp", "configured": true,
    "fields": ["token", "phone_id", "verify_token", "app_secret"] },
  { "provider": "sms", "configured": true,
    "fields": ["account_sid", "auth_token", "from_number"] },
  { "provider": "email", "configured": true,
    "fields": ["imap_host", "user", "pass", "smtp_host", "imap_port", "smtp_port", "from"] }
]
Try it · Connect, break and recover a channel

In a non-production environment, prove you understand the credential / availability split and the fail-safe behaviour:

  1. Configure SMS on the platform credentials card and point a Twilio test number’s webhook at /api/v1/webhooks/sms. Text the number and confirm a thread appears; reply and confirm delivery.
  2. Go to Channels and toggle SMS off. Text the number again — confirm no thread appears (inbound dropped). Toggle it back on; the next text should arrive.
  3. On the credentials card, Remove the SMS row. Have an agent reply on an existing SMS thread — confirm the message is still saved and shown (transcript intact) even though the network send was a no-op.
  4. Now repeat step 3 for an email thread with email creds removed — observe the difference: the reply is rejected (503) and not saved, so the agent can retry after re-configuring.
  5. Stretch: save a WhatsApp verify token but deliberately leave App secret blank. Confirm the GET verification handshake still passes, yet inbound POST messages are rejected (fail-closed) — then add the app secret and watch messages flow.

Success looks like: all three channels round-tripping a message, plus a clear, demonstrated grasp of the difference between disabled, no-creds, and the stricter email reply path.

14.8 Recap

Note · Where to go next

The agent experience of working these threads — the unified inbox, replying, transferring — is in the agent desktop chapter. The optional AI auto-responder that gets first crack at every inbound message is covered in the bot chapter. Per-reseller BYO entitlements and the reseller portal where resellers store their own keys are in the Resellers chapter.

CloudCX · Administrator & Training ManualCh. 14 · WhatsApp, SMS & Email
CloudCXCloudCX Administrator & Training Manual
Ch. 15 · Social Channels
Chapter 15

Social channels — Facebook, Instagram, X

When a customer messages your brand on Facebook Messenger, sends an Instagram DM, or opens a Direct Message on X (formerly Twitter), CloudCX treats that conversation exactly like a WhatsApp chat or an inbound web-chat: it lands in the omnichannel queue, the ACD routes it to an available agent, and the agent replies from the same workspace they use for every other channel. This chapter shows you how to connect all three social providers, store their credentials safely, verify the inbound webhooks each platform requires, and confirm that replies route back to the right person — with a fully worked Messenger connection and a hands-on Try-it exercise at the end.

One channel family, two webhook styles

The three providers collapse into two integration patterns. Facebook Messenger and Instagram DM both ride the Meta Graph API and share a single signed webhook surface — the same shape WhatsApp uses (Chapter 14). X uses its own Account Activity API with a CRC challenge and a different signature header. CloudCX hides that difference behind one uniform contract: every inbound message becomes an OmniThread + OmniMessage, and every agent reply is one POST /channels/<platform>/threads/{id}/reply call. Configure the credentials, point the webhook, and the rest of the platform behaves identically across channels.

15.1 How social messaging fits the platform

Each social platform has two halves inside CloudCX, and it helps to keep them straight before you touch any settings:

Crucially, the conversation model is channel-agnostic. A Messenger thread and an Instagram thread are the same kind of object as a WhatsApp or web-chat thread — only the channel field and the transport differ. That is why social conversations show up in the same agent inbox, count toward the same handle-time analytics, and obey the same routing rules you configured in Chapter 10. The diagram below traces a single inbound message from the customer’s tap to the agent’s screen.

Customer DM
Messenger · IG · X
CloudCX webhook
verify signature
Find / create
OmniThread
Bot tries first
opt-in AI
ACD route
& auto-assign
Agent inbox
live socket
The inbound social pipeline. Identical to WhatsApp/SMS once the signature is verified — only the parser and transport differ per provider.

The reply travels the same path in reverse: the agent’s message is persisted, broadcast to any open sockets, and pushed to the provider so the customer sees it in their app. We cover routing in detail in § 15.7.

Read this alongside Chapter 14

Messenger and Instagram are deliberately built as siblings of the WhatsApp adapter. If you have already connected WhatsApp, the Meta side of this chapter will feel familiar — the same App Secret, the same hub.challenge verification handshake, the same X-Hub-Signature-256 body signature. The differences are which IDs you store and which webhook fields you subscribe to.

15.2 The three providers at a glance

Before configuring anything, know what each provider needs from you. CloudCX advertises the exact credential fields per provider; the table below is the authoritative set (it mirrors the server’s PROVIDER_FIELDS). Every field marked secret is write-only — once saved it is encrypted at rest and never shown again.

Table 15.1 — Credential fields per social provider (required for a working channel)
ProviderTransportFieldsRecipient ID
Facebook Messenger Meta Graph Send API page_id, page_access_token (secret), app_secret (secret), verify_token PSID (Page-scoped ID)
Instagram DM Meta Graph Send API (linked Page) ig_id, page_access_token (secret), app_secret (secret), verify_token IGSID (IG-scoped ID)
X (Twitter) DM X API v2 · OAuth 1.0a api_key, api_secret (secret), access_token (secret), access_secret (secret), bearer_token (secret) X user id

Three points are worth internalising, because they explain almost every “why isn’t it working” question:

No credentials = a graceful no-op, not an error

CloudCX ships with no social credentials. Until you configure a provider, every outbound send for that channel is a silent no-op that returns {"ok": false, "disabled": true} rather than raising, and inbound webhooks for an unconfigured Meta channel are rejected because the App Secret check fails closed. This is by design: a half-configured tenant degrades gracefully instead of throwing errors into the agent UI. The flip side is that a missing field looks like “nothing happens” — so verify every required field is set.

15.3 Where social lives in the console

Two admin screens own social configuration, and you will move between them:

Open the console at admin.cloudcx.app, sign in as a platform administrator, and select Channels from the dark sidebar. The screen below is what you land on.

admin.cloudcx.app
Operate
Dashboard
Reports
Platform
Call Routing
Channels6
Platform Credentials
Security
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Channels// omnichannel · platform toggles
PRODAO
Omnichannel

Channels

The surfaces the platform supports and which shared credentials are configured for each.

Platform channel availability

WhatsApp — two-way WhatsApp Business
Email — ticketed email (IMAP / SMTP)
Social — social DMs, comments and mentions
Facebook
Not set
shared credentials
Instagram
Not set
shared credentials
X (Twitter)
Not set
shared credentials
1
2
3
4
  1. Channels nav item (Platform group) — the active screen. The pill shows the count of supported omnichannel surfaces.
  2. Platform channel availability — the master on/off switches. The Social umbrella toggle gates Messenger, Instagram and X together.
  3. Social switch — default ON. Turning it off silences all three social platforms platform-wide without deleting any credentials.
  4. Shared provider credentials summary — a read-only per-provider Configured / Not set status. Click through to Platform Credentials to edit.
The Channels panel. The Social umbrella switch and the per-provider credential status summary.
Two switches gate every social channel

A social platform is live only when both its per-platform flag and the umbrella social flag are enabled. Both default to ON, so a freshly configured channel works without touching these toggles. But either one set to off by an admin turns the channel off: the inbound webhook drops the message (logged as inbound_skipped_channel_disabled) and an agent reply is persisted but not sent. Use the umbrella Social switch as a single kill-switch during an incident.

15.4 Connecting Facebook Messenger

Messenger is the canonical Meta channel; once you have done it, Instagram is nearly identical. You will gather four things from the Meta side, then store them in CloudCX and point Meta’s webhook at your callback URL.

15.4.1 What to collect from Meta

Page ID  page_id
The numeric ID of the Facebook Page that customers message. CloudCX sends replies from this Page.
Page access token  page_access_token   secret
A long-lived Page access token with the messaging permission, generated in the Meta App dashboard. Used to call the Send API.
App secret  app_secret   secret
The Meta App Secret. CloudCX verifies the X-Hub-Signature-256 HMAC on every inbound event against it. Required — webhooks fail closed without it.
Verify token  verify_token
A string you invent. Meta echoes it during the one-time webhook handshake; CloudCX accepts the webhook only when the echoed token matches what you stored here.

15.4.2 Storing the credentials in CloudCX

Go to PlatformPlatform Credentials. You will find a card for Facebook Messenger among the provider cards. Fill it in and save.

  1. Open the Facebook Messenger card. Each provider has its own card with a status badge: Not set until configured, Configured afterward.
  2. Enter the Page ID. Paste the numeric Page ID into Page ID.
  3. Paste the Page access token. This is a secret field (masked); it is encrypted on save and never displayed again.
  4. Paste the App secret. Also secret. This is the value that lets CloudCX trust inbound webhooks.
  5. Set the Verify token. Type any hard-to-guess string — you will paste the same value into Meta in § 15.4.3.
  6. Save credentials. CloudCX validates that all required fields are present and non-empty, encrypts the payload (Fernet), and flips the badge to Configured. A missing field returns a clear 422; a server without its master key returns 503 and stores nothing.
admin.cloudcx.app
Platform
Channels
Platform Credentials
Security
ENCRYPTED AT REST
Fernet · BYOND_CREDS_KEY
Platform Credentials// shared provider & AI keys · encrypted
PRODAO
Connectivity & AI keys

Facebook Messenger

Meta Graph Send API. Stored encrypted; never shown again.

Not set

Facebook Messenger  // facebook

Page ID102837465120938
Page access tokenleave blank to keep current
App secret••••••••••••••••••••••••
Verify tokenbyondcx-msgr-7f3a91
Secrets are never shown. Leave a field blank to keep its current value.
Remove Save credentials
1
2
3
4
5
6
  1. Status badgeNot set until you save, then Configured.
  2. Page ID — the numeric Facebook Page ID replies are sent from.
  3. Page access token — secret; masked placeholder means “keep the stored value”.
  4. App secret — secret; the key that verifies inbound webhook signatures.
  5. Verify token — your chosen handshake string; paste the same value into Meta.
  6. Save credentials — validates, encrypts, and flips the badge.
The Facebook Messenger credential card. Two secret fields are masked; the App secret guards inbound webhooks.

15.4.3 Pointing Meta’s webhook at CloudCX

In the Meta App dashboard, under Messenger → Webhooks (or Webhooks for the Page product), configure the callback. CloudCX exposes a shared Meta endpoint and per-platform aliases — use whichever your setup prefers; they forward to the same handlers.

Callback URL (shared)https://cloudcx.app/api/v1/webhooks/meta
Callback URL (Facebook alias)https://cloudcx.app/api/v1/webhooks/facebook
Verify tokenthe exact string you stored in § 15.4.2
Subscribe tomessages (Page messaging events)
  1. Paste the callback URL and verify token into Meta and click Verify and save. Meta immediately sends a GET to your URL with hub.mode=subscribe, your hub.verify_token, and a hub.challenge nonce.
  2. CloudCX echoes the challenge. The handler compares the token to the stored Facebook verify_token (and, on the shared endpoint, the Instagram one too), and on a match replies with the raw hub.challenge value — which is exactly what Meta needs to mark the webhook Verified. A mismatch returns 403 and Meta shows the verification as failed.
  3. Subscribe the Page to the messages field. Without this subscription Meta verifies the URL but never actually delivers messages.
  4. Confirm in the CloudCX logs. A successful handshake logs social.meta_webhook_verified with channel=facebook.
One URL for both Meta products

The shared /webhooks/meta endpoint accepts either platform’s verify token (it tries Facebook then Instagram), so a single callback URL can verify for both Messenger and Instagram. At delivery time CloudCX reads the event’s top-level object field — "instagram" routes to the Instagram channel, anything else ("page") to Facebook — so the right App Secret is used to check the signature. If you prefer one URL per product, use the /webhooks/facebook and /webhooks/instagram aliases instead.

15.5 Connecting Instagram DM

Instagram messaging is authorised through the linked Facebook Page, so most of what you gathered for Messenger applies. The only differences from § 15.4 are the originating ID and the webhook field you subscribe to.

  1. Link the Instagram professional account to a Facebook Page in the Meta dashboard, and ensure the account is a Business or Creator (professional) account — personal accounts cannot use messaging.
  2. Collect the Instagram account ID (ig_id) — the professional account’s ID. The page access token, app secret and verify token are the linked Page’s values; in most setups they are identical to the ones you used for Messenger.
  3. Open the Instagram DM credential card under Platform Credentials and fill in ig_id, page_access_token, app_secret and verify_token. Save.
  4. Subscribe to Instagram messaging in the Meta webhook config — the Instagram product’s messages field. Use the shared /webhooks/meta URL or the /webhooks/instagram alias.
  5. Verify. On the handshake CloudCX logs social.meta_webhook_verified with channel=instagram; inbound DMs then arrive on the Instagram channel.
admin.cloudcx.app
Platform
Channels
Platform Credentials
ENCRYPTED AT REST
Fernet · BYOND_CREDS_KEY
Platform Credentials// instagram · linked Page
PRODAO
Connectivity & AI keys

Instagram DM

Meta Graph Send API via the linked Facebook Page.

Configured

Instagram DM  // instagram

Instagram account ID17841400000000000
Page access tokenleave blank to keep current
App secretleave blank to keep current
Verify tokenbyondcx-ig-7f3a91
Linked-Page token: in most setups this matches your Messenger credentials.
1
2
3
  1. Configured badge — the channel is ready once saved.
  2. Instagram account ID (ig_id) — the only field that differs from Messenger.
  3. Page access token / App secret — the linked Page’s values; blank keeps the stored secret.
The Instagram DM card. Only the account ID differs from the Messenger card; the token and secret come from the linked Page.

15.6 Connecting X (Twitter) Direct Messages

X is the one provider that does not use the Meta pattern. DMs ride the X API v2, writes are authenticated with OAuth 1.0a user-context, and the inbound webhook uses the Account Activity API with a CRC (Challenge-Response Check) handshake. You need five values, all entered on a single card.

API key  api_key
The X app’s consumer key.
API secret  api_secret   secret
The consumer secret. Also the key CloudCX uses to answer the CRC challenge and verify inbound event signatures.
Access token  access_token   secret
The connected account’s OAuth 1.0a user token.
Access token secret  access_secret   secret
The connected account’s OAuth 1.0a user-token secret. Together with the three above, this signs DM writes.
Bearer token  bearer_token   secret
App-only token for the Account Activity subscription and reads. Stored, but not sufficient to send DMs on its own.
  1. Create an X app with DM read/write and the Account Activity API enabled, then generate the consumer key/secret and the connected account’s access token/secret, plus a bearer token.
  2. Open the X (Twitter) DM card under Platform Credentials and enter all five values. Save — the badge flips to Configured.
  3. Register the webhook on the Account Activity API with the callback URL https://cloudcx.app/api/v1/webhooks/twitter.
  4. Pass the CRC challenge. X immediately GETs your URL with a crc_token. CloudCX replies with {"response_token": "sha256=" + base64(HMAC-SHA256(crc_token, api_secret))}. A correct response registers the webhook; an unset api_secret fails closed with 403.
  5. Subscribe the connected account to the Account Activity webhook so its DMs are delivered.
admin.cloudcx.app
Platform
Platform Credentials
ENCRYPTED AT REST
OAuth 1.0a · HMAC-SHA1
Platform Credentials// twitter · X API v2 · OAuth 1.0a
PRODAO
Connectivity & AI keys

X (Twitter) DM

X API v2 DM endpoint, signed with OAuth 1.0a user context.

Not set

X (Twitter) DM  // twitter

API key (consumer key)aBcD1234eFgH5678
API secret••••••••••••••••••••
Access tokenleave blank to keep current
Access token secretleave blank to keep current
Bearer tokenleave blank to keep current
API secret also keys the webhook CRC challenge and event-signature checks.
1
2
3
4
  1. Status badgeNot set until all five values are saved.
  2. API secret — the consumer secret; also keys the CRC and signature checks.
  3. Access token — the connected account’s OAuth 1.0a user token.
  4. Access token secret — pairs with the access token to sign DM writes.
The X (Twitter) DM card. Five OAuth fields; the API secret does double duty for webhook verification.
The bearer token alone cannot send DMs

A common misconfiguration is storing only the bearer token and expecting replies to work. CloudCX builds the DM’s Authorization: OAuth … header (HMAC-SHA1, RFC 5849) from the four OAuth 1.0a values. If any of api_key, api_secret, access_token or access_secret is missing, the send is a no-op (disabled: true) — inbound may still verify (it only needs api_secret), so you can receive DMs but silently fail to reply. Always provide all five.

15.7 Webhook verification and reply routing

This is the heart of the chapter: what happens when a message actually arrives, and how a reply gets back. The behaviour matches WhatsApp exactly — a valid signature is required, but once verified everything is processed defensively and the provider always gets a 200 so it doesn’t retry-loop on a transient error.

15.7.1 Inbound: verify, then route

Every inbound event runs the same gauntlet:

  1. Signature check (fail closed). Meta events must carry a valid X-Hub-Signature-256 HMAC over the raw body, keyed by that channel’s app_secret; X events must carry a valid X-Twitter-Webhooks-Signature keyed by api_secret. A missing, malformed or mismatched signature — or an unconfigured secret — returns 403 and the message is dropped.
  2. Kill-switch check. The per-platform flag and the umbrella social flag must both be enabled, or the message is skipped.
  3. Find or create the thread. CloudCX continues the first still-open thread for this (channel, sender id), else opens a new one. The sender’s scoped ID (PSID / IGSID / X user id) is stored as the thread’s visitor_contact — that is the address replies are sent to.
  4. Bot first crack. The opt-in AI auto-responder gets the turn first. If it handles the message, no human assignment happens; otherwise routing proceeds.
  5. ACD route and auto-assign. If the thread is unowned, CloudCX routes via the channel’s default queue, falls back to the presence picker, and assigns an available agent — logging social.thread_auto_assigned.
  6. Persist and broadcast. The inbound message is saved and broadcast over the live socket to any open agent/visitor view, then a 200 is returned to the provider.
Echoes and non-text are ignored, by design

CloudCX’s parsers are deliberately defensive. Messenger/Instagram echoes of your own outbound messages (is_echo), delivery/read receipts, reactions and non-text events are skipped. For X, an event whose sender_id equals the account the webhook is for (for_user_id) is treated as your own outbound and dropped. Anything unparseable is skipped rather than raising — so a malformed event never wedges the webhook.

15.7.2 Outbound: the agent reply

When an agent sends a reply, the workspace calls one endpoint per platform. All three share the same implementation:

FacebookPOST /channels/facebook/threads/{id}/reply
InstagramPOST /channels/instagram/threads/{id}/reply
X (Twitter)POST /channels/twitter/threads/{id}/reply

The reply path enforces, in order: tenant isolation (an agent may only reply on their own tenant’s thread), the channel kill-switch, and the prepaid / credit-limit gate. If the channel is disabled or the tenant is out of credit, the network send is skipped — but the outbound message is still persisted and broadcast so the transcript and the agent UI stay consistent. The send itself uses the resolved credentials; a missing-cred no-op is logged as reply_send_skipped and never surfaces an error to the agent.

cloudcx.app/agent
Conversation// Messenger · assigned to you
Messenger PRODAO
PSID 8841…20a · thread #4d9

Visitor (Messenger)

Channel facebook · status assigned

Open

inbound  Hi — is the downtown store open on Sunday?

Yes! We’re open 10am–6pm Sunday. Anything else?  outbound · you

ReplyType your message…
Add note Send reply
1
2
3
4
  1. Channel badge — the agent sees the social platform inline; the thread carries channel=facebook.
  2. Thread statusOpen/Assigned, exactly like any other omni thread.
  3. Inbound bubble — the parsed customer message, persisted as an OmniMessage.
  4. Send reply — calls /channels/facebook/threads/{id}/reply: persist, broadcast, then push via the Send API.
A Messenger conversation in the agent workspace. Social threads look and behave like every other channel.

15.8 Worked example — connect Messenger and verify end to end

Let’s connect a Page called Acme Retail and prove the round trip. We will store the credentials, verify the webhook, send ourselves a test message, and confirm the agent reply reaches Messenger.

  1. Gather four values from Meta. Page ID 102837465120938, a long-lived Page access token, the App Secret, and a verify token we invent: byondcx-msgr-7f3a91.
  2. Store them. In Platform Credentials → Facebook Messenger, fill in all four fields and click Save credentials. The badge turns Configured. Cross-check on Channels: the Facebook summary now reads Configured.
  3. Confirm the Social switch is on. On Channels, the Social umbrella switch is ON (the default). Leave it on.
  4. Set Meta’s webhook. Callback URL https://cloudcx.app/api/v1/webhooks/meta, verify token byondcx-msgr-7f3a91, subscribe to messages. Click Verify and save; Meta shows Verified and the CloudCX log shows social.meta_webhook_verified · channel=facebook.
  5. Send a test message. From a personal Facebook account, message the Acme Retail Page: “Is the downtown store open on Sunday?
  6. Watch it route. Meta delivers a signed POST. CloudCX verifies the signature, opens a new thread keyed by your PSID, the bot declines (no matching intent), the ACD assigns the thread to the on-shift agent, and the message appears in their inbox — logged as social.inbound · channel=facebook · new_thread=true.
  7. Reply as the agent. Type “Yes! We’re open 10am–6pm Sunday.” and click Send reply. CloudCX persists and broadcasts the message, then POSTs it to the Graph Send API as a RESPONSE-type message to your PSID.
  8. Confirm on the phone. The reply appears in Messenger within a second or two. The round trip is proven: webhook → thread → agent → Send API → customer.
# CloudCX structured log — a clean Messenger round trip INFO social.meta_webhook_verified channel=facebook INFO social.inbound channel=facebook new_thread=true mid=m_AbR…91 INFO social.thread_auto_assigned channel=facebook thread_id=4d9… agent_id=7c1… INFO social.sent channel=facebook to=8841…20a status=200
If the reply never reaches the customer

If inbound works but your reply doesn’t arrive, check in this order: (1) the Social umbrella switch and the Facebook switch are both ON; (2) the tenant is not out of prepaid credit (a blocked send logs reply_send_skipped_billing_blocked); (3) the page_access_token is valid and unexpired — an expired token returns an http_4xx in the social.send_http_error log; (4) the Messenger 24-hour standard messaging window has not closed for that conversation. CloudCX sends as a RESPONSE message, which is valid only within Meta’s allowed window after the customer’s last message.

15.9 Reseller bring-your-own credentials

Everything so far used CloudCX’s shared platform credentials, which serve every tenant whose reseller has not brought its own. A white-label reseller can instead supply its own social credentials — gated by the per-reseller Allow own social entitlement. When a reseller is both entitled and has stored credentials, its tenants’ social traffic uses those; otherwise it falls back to CloudCX’s shared creds, and finally to a disabled no-op if neither is set.

Webhook verification stays platform-level

Even when a reseller brings its own send credentials, the inbound webhook handshake and signature verification read the platform verify_token / app_secret / api_secret. In practice this means a BYO reseller’s inbound social messages flow through the platform’s registered webhook and App, while replies are sent from the reseller’s own Page/account — plan the App and Page ownership accordingly.

Try it — stand up Messenger in a sandbox and verify the webhook

Goal: connect a test Page, pass the webhook handshake, and observe an inbound message route to an agent — without spending real credit or risking production.

Done when: you can describe, from the logs, the exact path a Messenger message takes from Meta’s POST to the agent’s screen, and you have seen the signature check both pass and reject.

Key takeaways
CloudCX Administrator & Training Manual  ·  Chapter 15 — Social channels: Facebook, Instagram, X
CloudCXCloudCX Administrator & Training Manual
Ch. 16 · Unified inbox
Chapter 16

The unified agent inbox and cross-channel routing

Every customer conversation — a phone call, a web chat, a WhatsApp thread, an SMS, an email, a Messenger or Instagram DM, an X (Twitter) mention — lands in one place: the agent’s unified inbox on the Agent Desktop at cloudcx.app/agent. This chapter explains how that single pane is fed, how an interaction is routed to the right person, and how presence — whether an agent is Available, on Break, in Wrap-up or Offline — gates the whole loop. By the end you will be able to trace one chat message all the way from “visitor hits send” to “the right agent’s inbox lights up,” and explain every decision the platform made along the way.

What you will learn

How the channel-agnostic thread model lets six channels share one inbox; the four agent presence states and how to set them; the end-to-end routing loop (queue → skill check → live presence → strategy → auto-assign); the four queue distribution strategies and when each picks whom; the difference between auto-assignment and an agent claiming a thread; and a fully worked example you can reproduce in a test tenant.

This chapter sits on top of two you have already met. Chapter 14 — ACD & skills-based routing taught you to build queues, define skills and staff agents; Chapter 15 — Digital channels connected WhatsApp, SMS, email and the rest. Here we join those two: we show how a configured queue and a connected channel produce the live experience an agent actually works. You administer this loop, but you do not babysit it — once queues, skills and presence are right, routing runs on its own on the request path, every time a conversation starts.

16.1 One inbox, every channel

The defining promise of an omnichannel contact centre is that an agent does not juggle a phone, a chat window, an email client and three social apps. They work a single, prioritised list of interactions, and the workspace reshapes itself around whichever one they open. CloudCX delivers this with three columns that never change position:

The trick that makes this possible is that every digital conversation is the same shape underneath. CloudCX does not have a “chats table,” a “WhatsApp table” and an “email table.” It has one channel-agnostic pair of tables, and a channel discriminator on each conversation. Web chat was the first channel built on it; email, WhatsApp, SMS and social reuse the exact same machinery and differ only in their transport adapter. Voice is the one exception — it rides the SIP softphone and arrives as a screen-pop rather than a thread row — but it shares the same presence, the same context panel and the same disposition.

The unified-inbox data shape — one thread model, many channels
Web chatchannel=webchat
WhatsAppchannel=whatsapp
SMSchannel=sms
Emailchannel=email
Socialchannel=facebook…
OmniThreadone conversation

A single OmniThread row represents one conversation on one channel; OmniMessage rows are the inbound (from the customer) and outbound (from the agent) turns within it. New channels add an adapter, not a new table — which is why the inbox can show them all in one list.

16.1.1 The thread, in the fields you will see

You will meet these fields in the inbox, in analytics and in any API you script against. They are worth learning by name because every channel uses them identically.

channelwebchat · whatsapp · sms · email · facebook · instagram · twitter
The discriminator. Drives the row icon, the colour, and which centre pane opens. voice exists as a channel for queues but does not create a thread row — calls arrive via the softphone.
statusopen · assigned · closed
open = waiting for an agent (unrouted, or routed but not yet claimed). assigned = an agent owns it now. closed = wrapped up and removed from the live inbox. The inbox shows only open + assigned.
tenant_id
The owning tenant. An agent only ever sees their own tenant’s threads; an anonymous, not-yet-routed thread (tenant NULL) is visible only to a platform admin.
assigned_user_id
The agent currently handling the thread. Set by auto-assignment at creation, or when an agent claims an open thread. Cleared (set NULL) if that user is deleted.
visitor_name · visitor_contact
The customer’s identity for the conversation — a display name and a contact handle (email / phone / chat id), used to match a CRM record in the context column.
last_message_at
Timestamp of the latest turn. The inbox sorts by this (newest first), so the most recently active conversation rises to the top of an agent’s list.
Tip — “assigned” is not “answered”

A thread flips to assigned the moment routing picks an owner — before the agent has typed a word. Treat assigned as “has an owner,” not “is being actively handled.” The agent’s first outbound message is what the customer experiences as “answered,” and your analytics measure that separately.

CloudCX · Administrator & Training ManualCh. 16 · Unified inbox
CloudCXCloudCX Administrator & Training Manual
Ch. 16 · Unified inbox

16.2 The Agent Desktop at a glance

Here is the workspace an agent lives in all day. Read it left to right: the dark topbar carries identity, the agent-state selector and live counters; the three columns below it are the inbox, the active interaction and the customer context. The figure shows a WhatsApp conversation open in the centre stage.

cloudcx.app/agent
Inbox 7 interactions
All WA Chat Voice
RT Rosa Tan
WhatsApp · +65 8…41
Is my refund processed yet?
0:24 Live
JF Jamie Fox
Web chat · guest
Hi, I can’t log in…
2:10 Wait
PK Priya Kaur
Email · priya@…
Re: invoice #4471
14m Idle
Rosa Tan// WhatsApp · live
Available · 01:12 Handled 24 AO
RT Rosa Tan
+65 8123 4541 · WhatsApp
LIVE
Hi, is my refund processed yet?
Let me check that for you right now, Rosa.
Thank you!
Type a message… Send
Customer
Rosa Tan
VIP · Priority
AI Assist
Sentiment: neutral · refund query
1
2
3
4
5
6
  1. Channel filter chips — All, plus one per connected channel (Voice, Chat, WhatsApp, SMS, Email, Messenger, Instagram, X). Tap to narrow the list to a single channel.
  2. Inbox list — interactions newest-activity-first, each with a channel icon, contact, snippet and a wait badge (Live / Wait / Idle). The active row carries the gradient bar.
  3. Agent-state pill — the current presence state and its running timer. This is the single control that turns routing to this agent on or off (§ 16.3).
  4. Live counters — Handled / AHT / Answered / Open chats, refreshed from analytics every ~30 seconds.
  5. Centre stage — the active interaction. For a digital channel this is the message thread + composer; for voice it is the softphone; for email, the reader + reply box.
  6. Context column — matched CRM record, history, notes, AI Assist and the disposition form (off-screen below).
The Agent Desktop — one inbox feeding a channel-aware centre stage and a shared context column.
How the inbox stays live

The desktop polls the platform for the agent’s active threads roughly every 5 seconds and refreshes the headline counters every 30 seconds. While a conversation is open, its messages also stream over a live WebSocket, so new turns appear instantly rather than waiting for the next poll. You do not configure any of this — it is the desktop’s built-in behaviour — but it explains why a newly routed chat can take a second or two to surface in an idle agent’s list.

CloudCX · Administrator & Training ManualCh. 16 · Unified inbox
CloudCXCloudCX Administrator & Training Manual
Ch. 16 · Unified inbox

16.3 Presence: the on/off switch for routing

Routing only ever sends work to an agent who is Available. Presence is therefore the most important live signal in the whole loop — it is the agent’s own declaration of whether they can take the next interaction. CloudCX models exactly four states, set from the pill at the top-left of the desktop:

Table 16.1 — The four agent presence states
StateInternal valueRoutable?What it means on the desktop
Availableavailable YesReady · routing on. The agent is eligible to receive new interactions for every queue they staff.
On Breakbreak NoPaused · no new work. The agent keeps any interaction already open but receives nothing new.
Wrap-up (ACW)acw NoAfter-call work. The post-interaction window for notes and disposition; no new work arrives until they go Available again.
Offlineoffline NoLogged out of the queue. The agent is present in the app but excluded from all routing.

Only available is routable; the other three all mean “don’t send me anything new.” That is the entire contract. Presence is ephemeral, live state — it is not stored in the database. It lives in a fast in-memory store (CloudCX Cache) as a single roster that every part of the platform reads consistently, so a routing decision made on any worker sees the same picture. Each entry carries the agent’s id, name, current state and a since timestamp that is rewritten on every state change — a detail that matters a great deal for fairness, as § 16.5 shows.

cloudcx.app/agent
Available 01:12
Available
Ready · routing on
On Break
Paused · no new work
Wrap-up (ACW)
After-call work
Offline
Logged out of queue
1
2
3
4
  1. State pill — shows the live state and a colour dot; click it to open the menu below.
  2. State timer — time elapsed in the current state, useful for break and wrap-up discipline.
  3. Available — the only routable choice; its sub-label reads “Ready · routing on.” A tick marks the current selection.
  4. Break / Wrap-up / Offline — the three non-routable states, each with a plain-language sub-label.
The agent-state selector — one click sets presence and gates all routing to this agent.

16.3.1 Setting your state — procedure

  1. Open the state menu. Click the presence pill at the top-left (it shows your current state and a timer).
  2. Pick a state. Choose Available, On Break, Wrap-up (ACW) or Offline. The desktop posts the change to the platform immediately and the pill, dot colour and timer update.
  3. Confirm routing followed. When you go Available, your entry in the live roster flips to routable and you become eligible for the next interaction on every queue you staff. Any other state removes you from selection at once.
Warning — presence does not pull a conversation away mid-handle

Going On Break, Wrap-up or Offline stops new work; it does not drop an interaction the agent already owns. A chat assigned to an agent who then goes Offline stays assigned to them until it is closed or reassigned by a supervisor. Coach agents to finish or hand off open work before logging out — otherwise a customer can be left waiting on an absent owner.

CloudCX · Administrator & Training ManualCh. 16 · Unified inbox
CloudCXCloudCX Administrator & Training Manual
Ch. 16 · Unified inbox

16.4 The routing loop, end to end

Now the heart of the chapter. When a digital conversation starts — a visitor sends the first chat, a WhatsApp arrives at your number — the platform runs a short, deterministic routing decision on the spot, before the thread is even returned. That decision joins three sources and either auto-assigns an owner or leaves the thread open for someone to claim. Here is the whole loop:

Routing a new conversation — the decision the platform makes at thread creation
1 · Conversation startsA new OmniThread is created (status open) for a channel — e.g. webchat. If an AI bot is enabled for this tenant+channel, the thread is left open for the bot and human routing is skipped.create
2 · Find the queueThe channel’s queue is resolved — for webchat, the platform get-or-creates a stable Default Webchat queue if none is configured. The queue carries the distribution strategy and any required skills.queue
3 · Eligible membersTake the queue’s staffed members; keep only those who hold every required skill at or above its min_level. A queue with no required skills keeps all members.skills
4 · Available nowIntersect that set with the live presence roster — keep only the eligible members whose state is available right now. Everyone on Break / ACW / Offline drops out here.presence
5 · Apply the strategyAmong the survivors, the queue’s strategy picks exactly one: longest-idle, round-robin, fewest-calls or priority (§ 16.5).strategy
6 · Assign or leave openIf an agent was chosen, the thread is set to assigned with that assigned_user_id — it appears in their inbox. If nobody was eligible/available, the thread stays open for any agent to claim later.assign

Two design choices in this loop are worth calling out because they shape day-two behaviour:

Tip — the bot gets first refusal

If you have enabled an AI auto-responder for a tenant+channel (Chapter 17), step 1 short-circuits: the thread is left open so the bot can field the opening message, and human routing only runs if and when the bot hands off. Where no bot is configured, the loop above runs exactly as shown — this is the default.

Voice routes the same idea, a different path

An inbound call follows the same queue → skills → presence → strategy logic, but the result is delivered as a screen-pop on the chosen agent’s desktop and a ringing SIP softphone, not a thread row. The voice path is covered in Chapter 12; this chapter focuses on the digital channels that produce inbox threads.

CloudCX · Administrator & Training ManualCh. 16 · Unified inbox
CloudCXCloudCX Administrator & Training Manual
Ch. 16 · Unified inbox

16.5 The four distribution strategies

Step 5 of the loop — choosing one agent from the eligible, available set — is governed by the queue’s strategy. You set it when you build the queue (Chapter 14); here is what each one actually does and when to reach for it. In every case the candidate pool is identical (members who hold the skills and are Available now); only the tie-break differs.

Table 16.2 — Queue distribution strategies
StrategyPicks…Best for
Longest idle default The agent who has been Available longest — the oldest since timestamp. Because since is rewritten every time an agent changes state (including flipping back to Available after wrap-up), “longest idle” naturally rotates work evenly. Almost everything. Fair by default, needs no extra state, and is the sensible starting point for any new queue.
Round robin The next agent in a fixed rotation, advanced by a per-queue cursor over a stable ordering. Each new interaction steps to the next member, wrapping around. Teams that want a strictly predictable “take turns” rotation rather than idle-time fairness.
Fewest calls The least-loaded agent — the one currently handling the fewest live (open/assigned) interactions. Ties fall back to longest-idle. Blended workloads where you want to even out concurrent conversations, not just turns.
Priority The most proficient agent first — ordered by their highest skill level (descending). Ties fall back to longest-idle. Queues where seniority should answer first — e.g. escalations or VIP lines staffed by mixed-tier agents.
Why “longest idle” is the safe default

Longest-idle gives you fair, deterministic rotation for free — it reads the same since timestamp that presence already maintains, with no separate counter to drift out of sync when agents log in and out mid-shift. Round-robin’s cursor and fewest-calls’ live counts are more specialised; reach for them only when you have a concrete reason. If in doubt, leave a queue on longest-idle.

16.5.1 A side-by-side: same pool, four answers

Suppose three Available agents staff a queue at the moment a chat arrives. Watch how each strategy picks differently from the identical pool:

Pool
Ana (idle 5:00, 0 chats, skill 5) · Ben (idle 0:30, 2 chats, skill 3) · Cleo (idle 2:00, 1 chat, skill 4)
Longest idle →
Ana — oldest since (5:00)
Fewest calls →
Ana — 0 live chats
Priority →
Ana — highest skill level (5)
Round robin →
Whoever the cursor lands on next — e.g. Ben, then Cleo, then Ana, regardless of idle/load/skill

In this snapshot three of the four strategies happen to agree on Ana; round-robin alone ignores the live signals and simply takes turns. Change one number — give Ben skill level 6 — and Priority now picks Ben while the others still pick Ana. This is exactly the kind of behaviour you should reason about when you choose a strategy for a real queue.

Warning — strategy is moot if nobody is eligible

No strategy can conjure an agent. If every member is on Break, or none holds the queue’s required skill at the demanded level, the eligible-and-available pool is empty and the thread is simply left open. When chats pile up unrouted, the cause is almost always staffing or skills, not the strategy — check the queue’s live available-now count first (Chapter 14).

CloudCX · Administrator & Training ManualCh. 16 · Unified inbox
CloudCXCloudCX Administrator & Training Manual
Ch. 16 · Unified inbox

16.6 Auto-assignment vs. claiming

A thread reaches an agent by one of two routes, and it is important to know which is which because they behave differently in the inbox.

Auto-assignment

The routing loop chose an owner at creation. The thread is already assigned to that agent and lands directly in their inbox — they did nothing to receive it. This is the normal path whenever a suitable agent is Available.

Claiming

No one was eligible/available at creation, so the thread is open and waiting. An agent picks it from the inbox and claims it — assigning it to themselves and flipping it to assigned. This is the pull model for overflow and for queues with intermittent staffing.

Both routes converge on the same end state — an assigned thread with an assigned_user_id — and from there the agent handles and then closes it identically. Selecting a row in the inbox triggers a claim automatically if the thread is still open, so in practice an agent simply clicks the conversation they want and the platform sorts out ownership.

16.6.1 Handling and closing an interaction — procedure

  1. Be Available. Set your presence to Available so auto-assignment can reach you, or so newly claimed work counts you as active.
  2. Open the interaction. Click a row in the inbox. If it was open, this claims it to you (status → assigned); its history loads in the centre stage and a live socket opens for new turns.
  3. Handle it. Reply in the composer (chat / WhatsApp / SMS / social), write a reply (email), or work the softphone (voice). The right-hand column shows the matched contact, history and AI Assist throughout.
  4. Wrap up. In the Disposition panel, choose an Outcome (Resolved, Callback scheduled, Escalated, Pending customer, Sale/upsell, No interest, Wrong number), add tags and wrap-up notes for the CRM.
  5. Close, or Save & next. Save records the disposition and flips you to Wrap-up (ACW). Save & next closes the thread (status → closed, it leaves the live inbox), sets you back to Available and pulls the next interaction.
Tip — “Save & next” is the rhythm of a busy shift

Save & next is the one-click loop that closes the current conversation, returns the agent to Available and immediately surfaces the next one. It is what keeps a high-volume queue flowing — teach it as the default closing action. Plain Save (which parks the agent in Wrap-up) is for when they genuinely need after-call time before taking more.

Tenant isolation is enforced on every action

An agent can only see, claim, message or close a thread in their own tenant. Attempting to act on another tenant’s conversation is refused exactly as if the thread did not exist. Anonymous, not-yet-routed threads (tenant NULL) are visible and claimable only by platform admins. This is not a UI convenience — it is enforced server-side on every read and write, and you cannot configure it away.

CloudCX · Administrator & Training ManualCh. 16 · Unified inbox
CloudCXCloudCX Administrator & Training Manual
Ch. 16 · Unified inbox

16.7 Worked example — one chat, traced end to end

Let us follow a single WhatsApp conversation through the entire loop, naming every decision. Setup: tenant Northwind Retail runs a Support webchat/WhatsApp queue on the longest-idle strategy, requiring the skill billing at min_level 2. Three agents staff it; here is the live picture the instant a customer sends “Is my refund processed yet?”

Table 16.3 — The live picture at the moment the chat arrives
AgentPresencebilling skillAvailable sinceEligible & available?
Maya Availablelevel 309:02 (4m ago) Yes
Devin Availablelevel 109:05 (1m ago) No — skill too low
Sofia On Breaklevel 4 No — not available

Now the loop runs, step by step:

  1. Conversation starts. A new OmniThread is created, channel=whatsapp, status=open, visitor_name="Rosa Tan". No bot is enabled for this tenant+channel, so human routing proceeds.
  2. Find the queue. The platform resolves Northwind’s Support queue — strategy longest_idle, required skill billing ≥ 2.
  3. Eligible members. All three are members, but only those with billing ≥ 2 survive: Maya (3) and Sofia (4). Devin (1) is filtered out for insufficient skill.
  4. Available now. Intersect with the live roster: Sofia is On Break, so she drops. Only Maya remains.
  5. Apply the strategy. Longest-idle picks the oldest since among survivors. With one survivor, the answer is Maya.
  6. Assign. The thread is set assigned with assigned_user_id = Maya. Within a poll cycle (~5 s) the WhatsApp row appears at the top of Maya’s inbox with a Live badge; Rosa’s CRM record loads in the context column.

Variation A — Maya goes on Break first. If Maya had stepped away a second earlier, the eligible-and-available pool would be empty (Devin lacks the skill, Sofia and Maya are on Break). The thread would stay open; the next agent to become Available with billing ≥ 2 could claim it, or it would sit in the inbox as a waiting interaction.

Variation B — the queue had no required skill. Drop the billing requirement and Devin re-enters the pool. With Maya idle 4 minutes and Devin 1 minute, longest-idle still picks Maya. Switch the queue to round-robin and the cursor decides instead — it might hand this one to Devin and the next to Maya, taking turns regardless of idle time.

What an administrator takes from this

Every routing outcome reduces to three levers you control: who is staffed (membership), what they can do (skills vs. the queue’s requirement), and who is Available (presence) — with the strategy breaking the final tie. When work routes “wrong,” walk those four in order; the answer is always in one of them.

CloudCX · Administrator & Training ManualCh. 16 · Unified inbox
CloudCXCloudCX Administrator & Training Manual
Ch. 16 · Unified inbox

16.8 Troubleshooting the routing loop

When conversations are not reaching agents the way you expect, this table maps the symptom to the lever to check. Work it top to bottom — the common causes are first.

Table 16.4 — Routing symptoms and where to look
SymptomMost likely causeWhere to check / fix
Chats arrive but no one is assigned (all stay open)No eligible, Available agent at creation timeQueue’s live available-now count (Ch. 14). Confirm members are set to Available; confirm at least one meets every required skill.
One agent gets everything; others get nothingSkills or membership gap — only that agent is eligibleAgent skills vs. the queue’s required skill & level; queue membership of the others.
Work is not rotating fairlyStrategy mismatch, or agents not returning to AvailableThe queue’s strategy (longest-idle is fairest); coach agents to use Save & next so they flip back to Available promptly.
An agent on Break still “has” a chatIt was assigned before they changed stateExpected — presence stops new work only. Reassign open work via Supervision (Ch. 18) if the agent is gone.
Agent sees nothing in their inboxWrong tenant, or simply no active threadsConfirm the agent’s tenant matches the conversations; remember the inbox shows only open+assigned.
A brand-new tenant routes oddly before queues existThe fallback picker (longest-idle anywhere) is in playBuild the proper queue, skills and membership; the skills-based path takes over as soon as a queue is configured.
Try it — drive the routing loop yourself

In a test tenant with a webchat queue and two agent logins (call them A and B), prove to yourself how each lever moves the outcome:

  1. Both Available. Sign both agents in and set each to Available. Start a web chat (from the public widget or a second browser). Note which agent it auto-assigns to and watch the row appear in their inbox within a few seconds.
  2. Take one offline. Set agent A to Offline. Start another chat — confirm it now routes to B every time, because A is no longer routable.
  3. Empty the pool. Set B to On Break as well, then start a third chat. Confirm it stays open (unassigned) — nobody is eligible and Available.
  4. Claim from the inbox. Bring B back to Available, open that waiting chat from their inbox, and confirm it flips to assigned to B the moment they open it.
  5. Add a skill gate. Require a skill on the queue that only A holds. With both Available, start a chat and confirm it routes to A only — B is filtered out for lacking the skill.
  6. Wrap and advance. As the owning agent, pick a Disposition outcome and hit Save & next; confirm the thread closes (leaves the inbox) and the agent returns to Available.

Debrief: You have now exercised all four levers — presence, membership, skills and the claim path — and seen the thread move through openassignedclosed. This is the exact loop every live conversation runs through.

16.9 Glossary & what’s next

Unified inbox
The single, channel-agnostic list of interactions an agent works, with a centre stage that adapts to each conversation’s channel.
OmniThread / OmniMessage
The one conversation record (per channel) and the inbound/outbound turns within it — the shared shape behind every digital channel.
Presence
An agent’s live, ephemeral state — available, break, acw or offline — held in a fast in-memory roster. Only available is routable.
Routing loop
The decision at thread creation: queue → required-skill filter → live presence → strategy → auto-assign or leave open.
Strategydistribution
How a queue breaks the final tie: longest-idle (default), round-robin, fewest-calls or priority.
Claim
An agent taking ownership of an open thread from the inbox, flipping it to assigned to themselves.
Dispositionwrap-up
The outcome, tags and notes an agent records as they close an interaction, feeding the CRM and analytics.
ACWAfter-Call Work
The wrap-up presence state in which an agent finishes notes/disposition and receives no new work until they go Available.
What’s next

You can now trace any conversation from arrival to assignment and explain every decision the platform made. The neighbouring chapters build on this loop: Chapter 17 — AI assist & automation shows the bot that can field the opening message (and the AI Assist panel beside every thread); Chapter 18 — Supervision & quality shows how a supervisor watches this inbox live, reassigns stuck work and scores it; and Chapter 19 — Analytics & reporting turns the handled, closed threads into the numbers on the topbar and beyond.

CloudCX · Administrator & Training ManualCh. 16 · Unified inbox
CloudCXCloudCX Administrator & Training Manual
Ch. 17 · Agent Desktop
Chapter 17

Agent Desktop — Training Walkthrough

The Agent Desktop is the single screen where a contact-centre agent lives all day. From one workspace they set their availability, take voice calls, and handle web chat, WhatsApp, SMS, email and social conversations — with the customer’s history, an AI assistant and a wrap-up disposition always within reach. This chapter is the hands-on training track: it reproduces every panel of the live desktop, walks each task step-by-step, and finishes with a fully worked shift and a guided Try it exercise.

Where this screen lives

Agents sign in at cloudcx.app/agent with their CloudCX username and password — the same identity an administrator provisions in PlatformUsers (Chapter 15). The desktop is a browser application; it needs microphone permission for voice and works best in a current Chrome or Edge tab kept open for the whole shift. Supervisors monitor the same conversations from the Supervisor console (Chapter 13).

17.1 The three-column workspace

After sign-in the desktop opens on a fixed three-column layout that never changes shape, so an agent always knows where to look. The top bar carries identity, the availability state and the live shift counters. Below it, the Inbox (left) lists every interaction waiting or in progress; the Stage (centre) is where the selected interaction is handled; and the Customer panel (right) shows who you are talking to, the AI assistant and the wrap-up form.

cloudcx.app/agent
Inbox · filters
All7
Voice
WhatsApp2
Web chat3
Email1
REGISTERED · SIP OK
ext 4021 · agent
Available// state 00:42 · ext 4021
Handled 18 AHT 4:12 Open 3 AO
Centre stage

Active interaction

Pick a conversation from the inbox to handle it here.

// no interaction selected
The customer profile, AI Assist and disposition appear on the right once a conversation is open.
1
2
3
4
  1. Identity & SIP health — your name, extension and the softphone registration state. SIP OK means voice is ready.
  2. Availability & state timer — the current presence state and how long you have held it. Click to change state (§17.2).
  3. Live shift counters — Handled, Average Handle Time (AHT) and Open conversations, refreshed from analytics throughout the shift.
  4. The Stage — the centre column where the selected interaction (voice, message or email) is worked.
The Agent Desktop at sign-in: top bar, omnichannel Inbox (left), Stage (centre) and Customer panel (right).

17.1.1 Reading the columns

Inbox (left)

Channel filter chips across the top; below them a scrolling list of interactions. Each row shows the channel icon, the contact, a one-line snippet, the wait/live state and an unread badge. A pulsing green dot marks a live conversation.

Stage (centre)

Switches automatically to match the selected channel: a softphone for voice, a chat thread for messaging, or an email reader for email. Only one interaction is on stage at a time.

Customer (right)

Profile card, AI Assist, the CRM record, recent interaction history, private internal notes, and the disposition (wrap-up) panel.

Top bar (full width)

Identity, the availability selector with its timer, SIP status, and the three live shift counters. Always visible.

CloudCX · Administrator & Training ManualChapter 17 · Agent Desktop
CloudCXCloudCX Administrator & Training Manual
Ch. 17 · Agent Desktop

17.2 Setting your state

Your availability state tells the routing engine whether to send you new work. It is the single most important control on the desktop: forget to go Available and the queue will skip you; forget to go On Break and a call will arrive while you are away from your desk. CloudCX records exactly four states, and the timer next to the pill shows how long you have held the current one.

cloudcx.app/agent
REGISTERED · SIP OK
ext 4021
Available// state 00:42
Top bar

Change state

Set availability

Available
Ready · routing on
Selected
On Break
Paused · no new work
Wrap-up (ACW)
After-call work
Offline
Logged out of queue
1
2
3
  1. State pill & timer — click it to open the menu. The pill colour and the thin accent line under the top bar both follow your state (green = Available, amber = Break, blue = Wrap-up, grey = Offline).
  2. Available — routing is on; the ACD may assign you the next queued voice call or digital conversation.
  3. The paused statesOn Break, Wrap-up (ACW) and Offline all stop new work from being routed to you, for different reasons.
The availability selector. Choosing a state posts it to the presence service so supervisors and the router see it immediately.
Table 17.1 — The four agent states
StateDotNew work routed?Use it when…
AvailablegreenYesYou are at your desk and ready to take the next interaction.
On BreakamberNoLunch, comfort break, coaching — you have stepped away on purpose.
Wrap-up (ACW)blueNoFinishing notes and disposition after a call before taking the next one.
OfflinegreyNoEnd of shift, or signed in but not yet on the floor.

Procedure — go Available at the start of your shift

  1. Sign in at cloudcx.app/agent and wait for the SIP status to read Registered · SIP OK. If it stays SIP offline, see §17.7.
  2. Open the state menu by clicking the availability pill at the top-left of the top bar.
  3. Choose Available. The pill turns green, its timer resets to 00:00 and your presence is published to the routing engine and the Supervisor console.
  4. Confirm the dot. The presence dot on your avatar (bottom-right) also turns green — that is the supervisor’s at-a-glance signal that you are ready.
Tip · the timer is a coaching tool

The state timer is exactly what your supervisor sees in their real-time wallboard. A long Wrap-up timer, or repeated short Break spells, are the patterns adherence reports flag. Keep an eye on it — not to game it, but because it is the honest record of your shift.

Warning · do not close the tab to take a break

Closing the browser tab signs you out, but the router can take a moment to notice. A call may already be ringing your softphone when you close it, which records as a missed interaction against you. Always set On Break or Offline first, then close the tab.

CloudCX · Administrator & Training ManualChapter 17 · Agent Desktop
CloudCXCloudCX Administrator & Training Manual
Ch. 17 · Agent Desktop

17.3 The omnichannel Inbox

Every conversation you can work appears in the Inbox, regardless of channel. Web chat, WhatsApp and SMS threads arrive automatically as customers reach out; voice calls ring through the softphone and pop a card (§17.4). The channel filter chips let you focus on one medium at a time, and the count in the column header tells you how many interactions match the current filter.

cloudcx.app/agent
All Voice WhatsApp Chat
María Solís
WhatsApp · +34 6•• ••• 210
My order hasn't shipped yet…
2m
wait
Visitor · web
Web chat · pricing page
Is the Pro plan monthly?
live
live
D. Okonkwo
Email · Invoice query
Re: duplicate charge on…
14m
idle
// stage
Click a row to claim and open it. The stage and the Customer panel fill in instantly.
1
2
3
4
  1. Channel filter chips — tap All, Voice, WhatsApp, Chat, SMS, Email, Messenger, Instagram, X or Social to narrow the list.
  2. Channel icon — colour-coded per channel (WhatsApp green, chat blue, email violet, voice magenta) so you can read the list at a glance.
  3. Contact & snippet — who it is and the latest line of the conversation, truncated to one row.
  4. Wait state & unreadlive (active), wait (queued, customer waiting) or idle, plus an unread-message badge.
The Inbox with three live interactions across WhatsApp, web chat and email. The selected row is highlighted with the brand accent.

Procedure — claim and open a conversation

  1. Scan the wait states. Prioritise rows marked wait — the customer is queued and waiting on a reply.
  2. Click the row. CloudCX claims the thread for you (it becomes assigned to your user), loads the full message history onto the Stage, and opens a live connection so new messages stream in.
  3. Read the context. The Customer panel on the right populates with the contact’s profile, CRM record and recent history before you type a word.
  4. Respond. Work the conversation on the Stage (§17.5 for messaging, §17.6 for email).
Claiming is per-tenant and exclusive

When you claim a thread it is assigned to you and removed from the unassigned pool, so two agents never type over each other. You can only ever see and claim conversations belonging to your own tenant — the desktop enforces that boundary for you.

CloudCX · Administrator & Training ManualChapter 17 · Agent Desktop
CloudCXCloudCX Administrator & Training Manual
Ch. 17 · Agent Desktop

17.4 Handling a voice call

When the ACD routes a call to you, your softphone rings and a prominent screen-pop slides in from the top-right with the caller’s number, the matched contact (if CloudCX recognises the number) and the queue the call came from. You answer or decline from the card; once connected, the Stage becomes a full softphone with hold, transfer, conference and a keypad.

cloudcx.app/agent
INCOMING · ext 4021
SIP · ringing
Available// incoming call
// softphone idle — answer to begin
Incoming call Support · EN
JL
Jordan Lee
+1 415 ••• 7782
matched contact
Decline Accept
1
2
3
  1. Queue badge — which queue routed the call (e.g. Support · EN), so you know which script and tone to use.
  2. Caller & matched contact — the calling number, the name if CloudCX recognises it, and a matched contact tag when the CRM has a record.
  3. Accept / DeclineAccept answers the call and opens the softphone; Decline releases it back to the queue for another agent.
The inbound screen-pop. The caller’s context appears before you answer, so you open with their name.

17.4.1 The softphone and call controls

Answering replaces the Stage with the softphone: the caller’s identity, a live call timer, a waveform while audio flows, and a grid of call controls. The same controls drive an outbound call you place from the keypad.

cloudcx.app/agent
ON CALL · 02:36
REC ●
Jordan Lee// voice · +1 415 ••• 7782
REC
Jordan Lee
+1 415 ••• 7782 · San Francisco
02:36
Mute
Hold ✓
Keypad
Transfer
Conference
Hang up
1
2
3
4
5
  1. Recording indicatorREC shows the call is being recorded under the tenant’s policy (announce it to the caller where the law requires).
  2. Call timer — counts the connected talk time; it feeds your AHT counter when the call ends.
  3. Mute & HoldMute silences your microphone; Hold parks the caller (they hear hold music) and is shown active here.
  4. Keypad & Transfer — the keypad sends DTMF tones (for IVRs); Transfer hands the call to another agent or queue.
  5. Conference & Hang upConference adds a third party to the call; Hang up ends it and drops you into Wrap-up.
The in-call softphone with the caller on hold. The control grid changes which buttons are live according to the call state.

Procedure — answer, hold, transfer

  1. Accept the call from the screen-pop (or click Answer on the softphone while it rings). Your state switches to busy and the timer starts.
  2. Greet by name. Use the matched contact name and the queue context from the pop.
  3. Place on hold if you need to check something: click Hold. The button highlights and the caller hears hold music. Click it again to resume.
  4. Transfer when needed. Click Transfer, choose the target agent or queue, and complete the hand-off. Use Conference instead if you want to stay on the line with a colleague and the customer together.
  5. Hang up. Click Hang up to end the call. The desktop moves you to Wrap-up and opens the disposition panel (§17.8).
Tip · warm vs cold transfer

A conference then drop is a warm transfer — you introduce the customer to your colleague before you leave. A straight transfer is a cold hand-off. Warm transfers feel better to the customer for anything sensitive; cold transfers are faster for simple routing.

CloudCX · Administrator & Training ManualChapter 17 · Agent Desktop
CloudCXCloudCX Administrator & Training Manual
Ch. 17 · Agent Desktop

17.5 Messaging conversations & AI Assist

Chat, WhatsApp and SMS share one messaging Stage: a threaded conversation with inbound bubbles on the left, your outbound replies on the right, day dividers, and a composer at the bottom. Canned responses (quick replies) sit above the composer for one-tap answers, and the right-hand AI Assist panel reads the conversation to give you a sentiment read, a summary and a suggested reply you can drop straight into the composer.

cloudcx.app/agent
María Solís// WhatsApp · +34 6•• ••• 210
LIVE
My order #SO-4471 hasn’t shipped yet. It’s been five days.
09:14
Hi María, I’m sorry for the wait — let me check that order for you right now.
09:15 ✓✓
⚡ Greeting ⚡ Order status ⚡ Apology + ETA
Your order shipped this morning — tracking is…
AI Assist ● ready
AnalyzeSummarize
Negative · −0.42
Summary
Customer chasing a 5-day-old undelivered order; frustrated but polite.
Suggested reply
Your order shipped this morning, María — here’s the tracking link…
Insert
1
2
3
4
5
  1. Threaded conversation — inbound messages left, your replies right with delivery ticks; day dividers separate sessions.
  2. Canned responses — one-tap quick replies (Greeting, Order status, Apology + ETA…) configured for your tenant; tap to drop the text into the composer, then edit before sending.
  3. Composer — type a reply and press the send button (or Enter); the focus ring shows the field is active.
  4. AI Assist actionsAnalyze returns sentiment, a summary, topics and a suggested reply; Summarize gives a concise wrap-up of the whole thread.
  5. Suggested reply — an AI-drafted next message; Insert places it in the composer so you can review and edit before sending.
A WhatsApp conversation on the messaging Stage with AI Assist showing sentiment, summary and a suggested reply.
How AI Assist works — and its limits

AI Assist is powered by CloudCX’s integration with CloudCX AI. When you click Analyze the platform sends the conversation to the AI service and returns a sentiment score from −1.0 (negative) to +1.0 (positive), a short summary, up to five topic tags and a suggested next reply. It is an aid, not an author: always read and edit a suggested reply before sending it, and never let the sentiment label replace your own judgement. If AI Assist shows not configured, your administrator has not yet enabled the AI feature for the tenant (Chapter 12) — the rest of the desktop works exactly the same without it.

Procedure — answer a chat with help from AI Assist

  1. Open the thread from the Inbox (§17.3). The conversation loads on the Stage and the customer profile fills the right panel.
  2. Click Analyze in the AI Assist panel to read the sentiment and the summary — a fast way to catch up on a long thread.
  3. Draft your reply. Tap a canned response for the routine part, or click Insert on the suggested reply to start from the AI draft.
  4. Personalise and send. Edit the text so it is accurate and in your own voice, then press send. Your reply appears on the right with delivery ticks.
  5. Close out. When the issue is resolved, record a disposition (§17.8); saving it closes the conversation.
CloudCX · Administrator & Training ManualChapter 17 · Agent Desktop
CloudCXCloudCX Administrator & Training Manual
Ch. 17 · Agent Desktop

17.6 Email & the Customer panel

Email opens a reader on the Stage — subject, the thread of messages, and a reply box at the bottom — rather than a chat bubble view, because email is longer-form and asynchronous. Throughout, the right-hand Customer panel stays in place so the contact’s record, history and your private notes follow you across every channel.

Email reader

The subject line, sender, recipients and date head the thread; each message is a card you can read in full. Compose your reply in the box at the bottom and click Send reply. Your signature is appended automatically.

Profile card

Name, contact handle and a VIP badge for priority customers. The initials avatar carries the brand gradient.

CRM record & history

Company, tier and account fields from the CRM, plus a Recent interactions list showing prior contacts and how each was resolved (resolved, callback, escalated, missed).

Internal notes

A private scratch-pad visible only to agents and supervisors — never to the customer. Use it for context the next agent will need.

cloudcx.app/agent
D. Okonkwo// email · Invoice query
Re: Duplicate charge on invoice INV-2207
from [email protected] · to support@ · 14m ago
Hi, I was charged twice for invoice INV-2207 this month. Could you reverse the duplicate and confirm? Thanks, Daniel.
↩ Reply to [email protected]
Hi Daniel — you’re right, I can see the duplicate. I’ve raised the reversal…
Send reply
DO
Daniel Okonkwo
VIP
CRM record
Company · Acme Corp
Tier · Enterprise
Acct · #AC-2207
Recent interactions
Call · billing resolved
Chat · upgrade callback
1
2
3
4
  1. Email thread — subject, participants and the full message history as readable cards.
  2. Reply box — compose and click Send reply; your tenant signature is appended for you.
  3. Profile & VIP — the contact card with a priority badge where the CRM marks the customer as VIP.
  4. CRM & history — account fields and prior interactions with their outcomes, so you never ask the customer to repeat themselves.
An email on the Stage with the persistent Customer panel: profile, CRM record and recent interaction history.
CloudCX · Administrator & Training ManualChapter 17 · Agent Desktop
CloudCXCloudCX Administrator & Training Manual
Ch. 17 · Agent Desktop

17.7 Wrap-up — recording a disposition

Every interaction ends with a disposition: an outcome, optional tags, and wrap-up notes. This is how the contact centre knows what happened, feeds reporting, and triggers any follow-up. While you complete it your state is Wrap-up (ACW) so no new work interrupts you. The disposition panel sits at the foot of the Customer column and is styled as a dark console card so it stands out as the final step.

cloudcx.app/agent
// wrap-up
Call ended. Record the outcome on the right, then Save & next to return to Available.
DispositionACW 02:14
Outcome
Resolved
Tags
billing refund complaint technical
Wrap-up notes
Reversed duplicate charge on INV-2207; confirmed by email.
SaveSave & next →
1
2
3
4
  1. Outcome — the single required field. Pick the result that best describes the interaction (see Table 17.2).
  2. Tags — optional topic labels (billing, refund, technical, retention, complaint). Tap to toggle; selected tags glow brand-magenta.
  3. Wrap-up notes — a short summary written for the CRM and the next agent.
  4. Save / Save & nextSave records the disposition and stays; Save & next records it, closes the interaction and returns you to Available for the next item.
The disposition panel after a billing call: outcome Resolved, two tags, and a one-line wrap-up note.
Table 17.2 — Disposition outcomes
OutcomeMeansTypical follow-up
ResolvedThe customer’s issue was fully handled.None.
Callback scheduledYou agreed to call the customer back.A callback task is created.
Escalated to Tier-3Passed to a specialist team.Tier-3 picks it up from the notes.
Pending customerWaiting on the customer for information.Re-opens when they reply.
Sale / upsellA sale or upgrade was made.Feeds revenue reporting.
No interestOutbound contact declined the offer.Recorded against the campaign.
Wrong numberThe contact was not reachable / not the right party.Number flagged on the lead.
Warning · pick an outcome before you save

The outcome is mandatory — the desktop will refuse to save and prompt you to select one. A missing or sloppy disposition breaks reporting and leaves the next agent blind, so make the note specific (“Reversed duplicate charge on INV-2207”), not generic (“sorted”).

17.7.1 Worked example — a complete WhatsApp interaction

This is the full arc of one digital interaction, end to end, the way you will run it dozens of times a shift.

  1. Go Available. María’s WhatsApp lands in the Inbox marked wait with the snippet “My order hasn’t shipped yet…”.
  2. Claim it. Click the row; the thread opens on the Stage and María’s profile and order history fill the Customer panel.
  3. Catch up with AI. Click Analyze: sentiment Negative −0.42, summary “chasing a 5-day-old undelivered order; frustrated but polite.”
  4. Reply. Tap the Apology + ETA canned response, then Insert the AI suggested reply, edit in the real tracking link, and send.
  5. Confirm resolution. María replies “Got it, thank you!” — sentiment on a re-analyze flips positive.
  6. Disposition. Outcome Resolved; tags shipping; note “Order SO-4471 shipped, tracking shared, customer satisfied.”
  7. Save & next. The conversation closes, your Handled counter ticks up, and you return to Available for the next item.
Try it · run your first shift in training mode

Goal: handle one interaction on each of three channels and record a clean disposition for every one.

Check: your Handled counter should read at least 3, and your supervisor’s wallboard should show every interaction with a recorded outcome and no missed entries.

17.8 When something looks wrong

Table 17.3 — Quick troubleshooting
SymptomLikely causeWhat to do
SIP status shows SIP offlineMicrophone permission denied, or the softphone failed to register.Allow microphone access in the browser, then reload the tab. If it persists, tell your supervisor — you can still take digital work.
No calls arrivingYou are not Available, or no agent has gone Available for the queue.Check the state pill is green and the timer is running.
AI Assist says not configuredThe AI feature is not enabled for your tenant.Work without it; ask your administrator to enable AI insights (Ch. 12).
Inbox is empty but customers are waitingThe live connection dropped.Reload the tab; claimed conversations re-appear automatically.
Can’t save a dispositionNo outcome selected.Choose an outcome from the dropdown, then save.
Where to go next

Supervisors monitor these same interactions, listen in and coach from the Supervisor console — Chapter 13. Outbound campaign dialling (the Campaign work mode in the Inbox) is covered in Chapter 18, and the AI features behind AI Assist are configured in Chapter 12.

CloudCX · Administrator & Training ManualChapter 17 · Agent Desktop
CloudCXCloudCX Administrator & Training Manual
Ch. 18 · Supervisor Wallboard
Chapter 18

Supervisor wallboard and live monitoring

The supervisor wallboard is the real-time pane of glass over a contact centre. It answers the three questions a floor supervisor asks every minute — What is happening right now? Who is free, who is busy, who is struggling? And can I step in to help this call? This chapter walks the live wallboard end to end: the KPI strip, the agents grid, live interactions, service levels, live voice queues and the outbound campaign monitor, then the live-coaching trio — monitor, whisper and barge — that let a supervisor join a call silently, coach the agent privately, or take part in three-way.

Where this lives

The wallboard is served at supervisor.cloudcx.app (the /supervisor app), separate from the admin console at admin.cloudcx.app. It is a dedicated, distraction-free surface meant to run full-screen on a team-leader’s monitor or a wall display. It reads from the same control plane through the shared API under /api/v1, and every figure on it is live — nothing on the wallboard is sample or demo data. Where a metric cannot be derived from real telemetry, the wallboard shows n/a rather than inventing a number.

18.1 Who can use the wallboard, and what they see

Access to the wallboard requires a sign-in with a supervisor or platform-administrator account. The control actions (monitor / whisper / barge) are admin-gated on the server — an ordinary agent token cannot originate an eavesdrop even if it reaches the page. Sign-in posts to /api/v1/auth/token; the resulting bearer token is held in the browser and attached to every poll. A 401 at any point clears the token and returns the user to the sign-in gate.

Tenant isolation. What a user sees on the wallboard is scoped to their place in the hierarchy, exactly as everywhere else in CloudCX:

Tip — leave it running

The wallboard polls every 3 seconds and automatically pauses when its browser tab is hidden (and resumes with an immediate refresh when it becomes visible again). It is designed to stay open all shift on a dedicated display; you do not need to refresh it by hand.

18.2 Anatomy of the wallboard

The wallboard is one continuously-scrolling page. From the top: a sticky topbar with the live clock and connection state; a six-tile KPI strip; the main grid pairing the Agents panel with a right-hand stack of Live interactions and Service levels; then a Voice contact-centre row (live queues + campaign monitor); and finally the Quality tooling (scorecards and evaluations, covered in Chapter 19). Figure 18.1 reproduces the top of the wallboard with the key regions marked.

supervisor.cloudcx.app
Live Wallboard// real-time operations · all queues
LIVE 14:32:07 SV
Live calls
7
// byond:live_channels
Agents online
9
of 11 staffed
On call
5
live conversations
Open chats
12
// open + assigned
Calls today
348
// since 00:00 UTC
Answer rate
94%
327 of 348 answered

● Agents

live · 11 agents
PT
Priya T.
#a1f0c2e9
ON CALL
● +44 7700 900 821
MonitorWhisperBarge
MK
Marcus K.
#7c5e11d0
AVAILABLE
● ready · waiting
MonitorWhisperBarge

Live interactions

● 7 active
● VOICE  +44 7700 900 8214:12 ● live
● VOICE  +1 415 555 01481:38 ● live

Service levels

// today · 348 calls
Avg handle
5:21
Answer rate
94%
1
2
3
4
5
6
  1. Live Wallboard title + subtitle — the surface name and the scope line (all queues, scoped to your tenant).
  2. Connection state + clock + identityLIVE while polling succeeds, flipping to RECONNECTING if the API stalls; your wall-clock time and signed-in avatar.
  3. KPI strip — six live tiles (the dark tile is Live calls); see § 18.3.
  4. Agents grid — one card per signed-in agent with state and the monitor/whisper/barge controls.
  5. Live interactions — the dark console list of every active call leg, with rolling duration.
  6. Service levels — avg handle, answer rate, abandon (today); wait metrics show n/a.
The supervisor wallboard, top section, with the six live regions marked.

18.3 The KPI strip

The six tiles across the top give the at-a-glance health of the operation. They refresh on every 3-second poll of /supervisor/overview (the answer-rate figures come from /analytics/summary and refresh every 30 seconds). Each tile names its data source in its small caption so you always know what the number means.

Table 18.1 — Wallboard KPI tiles and their sources
TileWhat it countsSource
Live callsActive voice channels right nowCloudCX Cache byond:live_channels (maintained by the telephony control socket consumer)
Agents onlineSigned-in agents not in the offline state, over total staffedagent:presence
On callAgents currently in the on call stateagent:presence
Open chatsOmnichannel threads in open or assigned statusDB — open_chats
Calls todayCDR rows created since 00:00 UTCDB — cdr_today
Answer rateAnswered ÷ total calls today, with the count beneath/analytics/summary (voice)
Note — “today” is UTC

The Calls today and Answer rate tiles count from 00:00 UTC, not your local midnight. On a wallboard read in another time zone the day boundary will appear to roll over off-hours; this is expected and keeps every tenant’s “today” on one consistent clock. Use Chapter 20 (Analytics) for time-zone-aware reporting.

CloudCXCloudCX Administrator & Training Manual
Ch. 18 · Supervisor Wallboard

18.4 The agents grid — who is doing what

The Agents panel renders one card per agent present in agent:presence. Each card shows the agent’s initials avatar, name, a short identity tag, a state pill, a one-line “now” summary of what they are doing, and the three live-coaching buttons. The card’s state pill is colour-coded so the floor is readable at a glance from across the room.

Table 18.2 — Agent states on the wallboard
StatePillMeaning & “now” line
AvailableavailableSigned in and ready — “ready · waiting”. Counts toward Agents available on queues.
On callon callIn a live conversation — the caller’s number is shown. The only state where coaching is enabled.
ACWacwAfter-call work (wrap-up / disposition). Not taking new contacts yet.
BreakbreakOn a break / away / paused. “on break”.
OfflineofflineSigned in but not ready (or just signed out). Not counted as online.

The presence layer reports a free-form state string and the wallboard normalises common synonyms onto these five buckets — for example ready/online map to available; busy/talking map to on call; wrapup/after_call map to ACW; away/pause/lunch/dnd map to break.

Tip — read the grid as a heat-map

A floor where most cards are available and a few are on call is healthy. A wall of break or offline during peak hours is your first signal that staffing or adherence needs attention — before the queue starts backing up.

18.5 Live interactions and service levels

To the right of the agents grid sits a two-panel stack. The dark Live interactions panel lists every active call leg from the same overview snapshot: a channel chip (VOICE today), the caller (caller-ID number, falling back to caller-ID name), the direction, a short channel-UUID, and a duration that ticks up live from the channel’s start timestamp. Bridged/answered legs carry a green ● live tag.

Below it, Service levels shows three calculated figures for today: average handle time, answer rate (answered ÷ calls) and abandon rate (unanswered ÷ calls). The abandon cell is tinted green at ≤ 5 %, amber at ≤ 10 % and red above that.

Warning — Avg/longest wait show n/a by design

The wallboard does not fabricate metrics it cannot measure. CloudCX does not yet expose per-call queue-wait telemetry, so the Avg wait service-level cell honestly shows n/a. Do not read a blank or n/a wait figure as “zero wait” — it means the data source is not available. The same honesty principle applies across the wallboard: a metric is either live or clearly flagged unavailable.

18.6 Live voice queues

The Voice queues panel lists each ACD voice queue your tenant owns, with its routing strategy badge and three live numbers. The queue list is read from /acd/queues (filtered to voice/any channels) and each queue’s figures from /acd/queues/{id}/stats; the list is re-read about every 30 seconds so a newly-created queue appears on its own, while the per-queue stats refresh every poll.

supervisor.cloudcx.app

● Voice queues

// live · 2 queues
Sales — Inboundlongest_idle
2
Calls waiting
4
Agents avail.
7
Staffed
Support — Tier 1round_robin
2
Calls waiting
3
Agents avail.
4
Staffed

● Campaign monitor

// live · 1 running · 2 total
Q2 Win-backprogressive running
38
Connected
126
Dialed
30%
Connect
874
Remaining
126 / 1000 worked13%
1
2
3
4
5
  1. Queue name + routing strategy — the strategy badge (longest_idle, round_robin or fewest_calls).
  2. Calls waiting — a live, best-effort indicator of inbound legs still ringing/early (see the note below).
  3. Agents available / Staffed — members of the queue currently available, and total members.
  4. Campaign + dial mode + status — running/paused outbound campaigns with their dialer mode.
  5. Connected / Dialed / Connect rate / Remaining + progress — the live dialer figures.
Live voice queues (left) and the outbound campaign monitor (right).
Note — how “Calls waiting” is derived

The live-channel snapshot is not tagged by queue, so a precise per-queue waiting count is not available. The wallboard therefore shows a platform-wide, best-effort indicator — the count of inbound legs in a ringing/early state that are not yet answered — on each voice queue. Treat it as a directional “is anyone waiting right now?” signal, and use Chapter 20 analytics for exact queue-level wait statistics.

18.7 The campaign monitor

Beside the queues, the Campaign monitor surfaces active outbound dialer work. It lists running and paused campaigns (falling back to all campaigns if none are active), each showing its dial mode — preview, progressive or predictive — a status pill, and four live figures from /campaigns/{id}/stats: Connected, Dialed, Connect rate and Remaining, with a progress bar of records worked against the campaign total. Campaign creation and dialer configuration are covered in the outbound campaigns chapter; here the focus is purely live observation.

CloudCXCloudCX Administrator & Training Manual
Ch. 18 · Supervisor Wallboard

18.8 Monitor, whisper and barge

The three live-coaching actions are the heart of supervision. Each one rings the supervisor’s own endpoint and bridges that answered leg into the target call — under the hood, a CloudCX Switch eavesdrop against the live channel. They differ only in who can hear the supervisor.

Monitor

Listen only. A silent spy: the supervisor hears both the agent and the customer, but neither party hears the supervisor. Used for quiet quality checks and live coaching prep.

Whisper

Coach the agent. The supervisor’s audio is injected into the agent’s ear only — the customer cannot hear it. Used to guide a live answer without the customer knowing.

Barge

Three-way. The supervisor becomes audible to both parties and fully joins the conversation. Used to rescue or take over a difficult call.

The flow is the same in all three cases: pick a live call → CloudCX rings the supervisor leg → on answer it is bridged into the call in the chosen mode.

Monitor / whisper / barge call flow
Supervisorclicks action
CloudCX APIPOST /supervisor/<kind>
CloudCX Switchoriginate & eavesdrop
Sup. phone ringssupervisor_dialstring
Bridged inlisten / whisper / 3-way

18.8.1 When the buttons are enabled

On each agent card, the three buttons are only enabled when that agent is on call and the wallboard has matched a live channel UUID to them. For an available, ACW, break or offline agent the buttons are greyed out — there is no live call to join. If you click an action when there is no live channel, the wallboard tells you so rather than failing silently.

channel_uuidrequired
The UUID of the live channel to spy on. Supplied automatically from the agent card; validated server-side as a real UUID before any command is built.
supervisor_dialstringdefault loopback/echo
The endpoint to ring for the supervisor leg — for example their SIP extension user/1001. Defaults to a self-contained loopback/echo so the control is testable without a registered supervisor phone. Validated against the allowed-endpoint pattern.
Warning — admin-gated, and audited

Monitor, whisper and barge are restricted to supervisor/administrator tokens on the server — an agent account cannot originate them. Every action is logged with its target channel. Because barge makes you audible to the customer, treat it as a deliberate intervention, not a casual click: announce yourself when you join.

18.8.2 Procedure — monitor a live call

  1. Find an on-call agent. In the Agents grid, locate a card showing the on call pill. Its “now” line shows the customer’s number, and its three buttons are active.
  2. Click Monitor. The button briefly disables while CloudCX rings your supervisor endpoint and bridges it into the call in listen-only mode.
  3. Answer your phone. When your supervisor extension rings, answer it. You will now hear both the agent and the customer; neither of them hears you.
  4. Confirm. A toast reads MONITOR started on <agent>. You are now silently monitoring.
  5. Escalate if needed. If eavesdrop_enable_dtmf is armed (whisper/barge pre-arm it), press 1, 2 or 3 on your phone keypad to switch between listen, whisper-style and three-way at runtime — without redialling.
  6. End the session. Hang up your supervisor leg. The agent’s call with the customer continues uninterrupted.
Tip — the runtime DTMF escalation path

Because whisper and barge pre-arm DTMF control on the spying leg, you can start on Monitor, listen for a minute, then press 2 to whisper a hint to the agent and 3 to fully barge in if the call is going sideways — all on the one connection. Press 1 to drop back to silent listening. This is the smoothest path from observing to intervening.

CloudCXCloudCX Administrator & Training Manual
Ch. 18 · Supervisor Wallboard

18.9 Worked example — coaching a struggling new agent

It is 14:30 on a busy afternoon. You are the floor supervisor for the Sales — Inbound tenant. A new hire, Priya T., has been live for two days. You want to keep an eye on her first few complex calls and step in only if she gets stuck.

18.9.1 Reading the board

  1. Scan the KPI strip. Live calls reads 7, Agents online 9 of 11, Answer rate 94%. Healthy — no fire to fight.
  2. Find Priya. Her card shows on call with the customer number +44 7700 900 821, and a duration ticking past 4:12 in Live interactions — longer than her usual handle time.
  3. Check the queue. Sales — Inbound shows 2 calls waiting and 4 agents available — you can afford to spend a minute coaching without starving the queue.

18.9.2 Stepping in, the right way

  1. Monitor first. Click Monitor on Priya’s card and answer your phone. You hear the customer pushing back on a price; Priya is hesitating.
  2. Whisper a hint. Press 2 to switch into whisper. You quietly tell Priya: “Offer the annual plan discount — it’s 15%.” The customer hears nothing.
  3. Let her recover. Priya makes the offer in her own words and the customer warms up. Press 1 to drop back to silent listening.
  4. Barge only if necessary. The customer asks a contract question Priya can’t answer. You press 3 to barge in, introduce yourself — “Hi, I’m the team lead, happy to help” — resolve the point, then hang up your leg, leaving Priya to close.
  5. Follow up. After the call, open Quality (Chapter 19) and score the interaction against the Voice — Sales QA scorecard, noting the strong recovery for Priya’s coaching log.
Outcome

You moved from silent monitor → private whisper → brief barge — the least-intrusive intervention at every step — and saved a sale without undermining the agent in front of the customer. That escalation ladder is the supervisor’s craft, and the wallboard is built around it.

Try it — run a supervising shift

On a training tenant with at least one agent signed in and a test call in progress (use the default loopback/echo supervisor endpoint if you have no SIP phone registered):

  1. Open supervisor.cloudcx.app and sign in. Confirm the topbar reads LIVE and the clock is ticking.
  2. Read each KPI tile aloud and name its data source from Table 18.1.
  3. Identify which agents’ coaching buttons are enabled, and explain why the others are greyed out.
  4. On an on-call agent, click Monitor; confirm the toast and the live-interactions duration ticking up.
  5. Trigger Whisper then Barge on the same call and articulate, for each, exactly who can hear you.
  6. Open Voice queues and read off calls waiting, agents available and staffed for one queue — then explain why calls waiting is a best-effort number.
  7. Find the Avg wait service-level cell and explain, in one sentence, why it shows n/a.

Check yourself: if you can defend every number on the board (or correctly say “that one is unavailable”), and you escalated monitor → whisper → barge in order, you are ready to supervise a live floor.

18.10 Troubleshooting

Table 18.3 — Common wallboard symptoms and what they mean
SymptomLikely cause / fix
Topbar shows RECONNECTINGThe overview poll failed. The wallboard retries automatically every 3 s; check API reachability and your session if it persists.
“no agents signed in”The agent:presence hash is empty — no agents are signed in (or presence isn’t being projected). Have an agent sign in.
“no live channels”byond:live_channels is empty — there are genuinely no active calls right now.
Coaching buttons greyed outThe agent is not on call, or the wallboard hasn’t matched a live channel UUID to them. Only on-call agents can be monitored.
Action toast: “… failed”A 422 means a bad channel UUID or supervisor dial string; a 502 means the eavesdrop couldn’t be originated — check the supervisor endpoint is reachable.
Service levels read everywhereAnalytics is unavailable; the panel degrades to dashes rather than guessing. Live calls/agents still update independently.
Sent back to the sign-in gateYour token expired (401). Sign in again; polling resumes automatically.
CloudCX · Administrator & Training ManualChapter 18 · Supervisor wallboard and live monitoring
CloudCXCloudCX Administrator & Training Manual
Ch. 19 · Quality
Chapter 19

Quality — scorecards and evaluations

Quality Management lets you turn a vague sense of “good service” into a repeatable, weighted score. You build a scorecard — a reusable rubric of criteria — then evaluate individual interactions against it, either by hand or with CloudCX AI doing a first pass. This chapter walks through building a scorecard, scoring a conversation, letting the AI auto-score, and a fully worked QA review you can repeat on the training track.

19.1 Where Quality lives

Quality tooling is part of the Supervisor workspace, not the admin console — it is the work of team leads and QA analysts who can also see live calls and monitor agents. Sign in at supervisor.cloudcx.app; the Quality scorecards, Evaluate interaction and Past evaluations panels sit at the bottom of the wallboard, beneath the live agents, service levels and voice queues.

Three things work together. A scorecard is the rubric. An evaluation is one scoring of one interaction against a scorecard. The interaction being scored is normally an omnichannel thread (a chat, WhatsApp, SMS or email conversation); a voice call can instead be referenced by its call UUID. The relationship is one rubric → many evaluations.

The Quality data model
Scorecardrubric · weighted criteria
Interactionomni thread · or call_uuid
Evaluationscores · weighted total
Agent + reviewerwho was scored · by whom

Two properties make the model durable. First, every evaluation stores its own weighted total_score and max_score at the moment you save it — so editing or retiring a scorecard later never silently rewrites history. Second, scorecards and evaluations are tenant-scoped: an analyst only ever sees the rubrics and scores belonging to their own tenant (platform administrators can additionally publish shared, tenant-less scorecards).

Reads vs. writes

Anyone signed into Supervisor can read scorecards and score interactions. Creating, editing or deleting a scorecard is an admin-gated write. If the + New scorecard form rejects your save with a permission error, ask a platform or tenant admin to publish the rubric — analysts then evaluate against it freely.

19.2 Anatomy of a scorecard

A scorecard has a name, an optional description, an active flag, and a list of criteria. Each criterion is the unit you score against, and carries four fields:

Keykey
Stable machine identifier, e.g. greeting. Lower-case, 1–64 chars. If you leave it blank the UI auto-derives it from the label (“Warm greeting” → warm_greeting). The key is what the AI scorer and the saved scores map are keyed by, so keep it short and unique within the card.
Labellabel
Human-readable name shown to the evaluator, e.g. Warm greeting & identification. Required, up to 255 chars.
Maxmax_points
The most an evaluator may award for this criterion. Must be greater than 0; defaults to 5. Awarded points are clamped to [0, max_points] server-side, so an over-generous entry can never inflate the score.
Weightweight
How much this criterion counts relative to the others. Defaults to 1; must be ≥ 0. A weight of 2 makes the criterion count double; a weight of 0 keeps it on the card for guidance but excludes it from the total.

The weighted maths is the heart of it. For each criterion, the points you award are clamped to its maximum and multiplied by its weight; that product is added to the total, while max_points × weight is added to the maximum. The percentage you see everywhere in the UI is simply total ÷ max × 100, rounded.

Scoring formula (computed server-side at save time)
# for every criterion on the scorecard
points   = clamp(awarded, 0, max_points)        # can't go below 0 or above the max
total   += points      * weight              # your earned, weighted points
max     += max_points  * weight              # the full weighted denominator

percentage = round(total / max * 100)         # the headline % score
Unscored criteria still count against you

The denominator is always the whole rubric. Every criterion contributes its weighted maximum to max_score regardless of whether you scored it — so a criterion you leave at 0 (or simply forget) lowers the percentage. If a criterion genuinely does not apply to a channel, build a separate scorecard for that channel rather than zeroing it.

CloudCX · Administrator & Training ManualCh. 19 · Quality — scorecards & evaluations
CloudCXCloudCX Administrator & Training Manual
Ch. 19 · Quality

19.3 Building a scorecard

Open the Quality scorecards panel on the left. Existing rubrics appear as cards, each showing its name, an active / retired badge, a criteria count, and chips previewing the first few criteria with their maximums. Below the list is the collapsible + New scorecard form.

supervisor.cloudcx.app
Operate
Wallboard
Agents14
Voice queues5
Governance
QualityQA
LIVE · ALL QUEUES
v4.8 · supervisor
Quality scorecards// 3 scorecards
LIVESV

Quality scorecards

3 scorecards
Voice — Sales QA active
Outbound sales · 5 criteria
Greeting · 5 Discovery · 10 Compliance · 5 Close · 10
Chat — Support QA active
Digital support · 6 criteria
+ New scorecard
1
2
3
4
5
  1. Quality nav group — the Governance section of the supervisor sidebar that holds the QA tooling.
  2. Source counter — live count of scorecards visible to your tenant (// 3 scorecards).
  3. Selected card — a pink ring marks the scorecard you have clicked; its criteria load into the scoring panel.
  4. Criteria preview chips — the first six criteria, each shown as label · max.
  5. + New scorecard — expands the create form (name, description and the criterion builder).
The Quality scorecards list, with one card selected.

19.3.1 The criterion builder

Expanding + New scorecard reveals a name field, a description field, and the criterion builder — a small grid with one row per criterion under the headers Key · Label · Max · Weight. A fresh form seeds one empty row; the + Criterion button adds more, and the × at the end of each row removes it.

  1. Open the form. Click + New scorecard at the bottom of the scorecards panel.
  2. Name it. Type a clear Name such as Voice — Sales QA. Add an optional Description to remind reviewers what the card is for.
  3. Add the first criterion. In the seeded row, type a Label (e.g. Warm greeting). Leave Key blank to auto-slug it, or type your own short key.
  4. Set Max and Weight. Enter the maximum points (default 5) and the weight (default 1). Both accept half-point steps.
  5. Add the rest. Click + Criterion for each further dimension. Rows with a blank label or key are silently dropped on save, so leave nothing half-filled.
  6. Save. Click Save scorecard. The card needs a name and at least one valid criterion, or the save is refused with a toast. On success the form collapses and the new card is auto-selected, ready to score against.
Designing good criteria

Keep cards to 4–8 criteria so a review takes minutes, not an afternoon. Use weight to express what your business actually cares about — weight compliance and resolution heavily, weight pleasantries lightly. Reuse the same key names (greeting, resolution, compliance) across cards so reports line up channel-to-channel.

CloudCX · Administrator & Training ManualCh. 19 · Quality — scorecards & evaluations
CloudCXCloudCX Administrator & Training Manual
Ch. 19 · Quality
supervisor.cloudcx.app

New scorecard

Name
Voice — Sales QA
Description
Outbound sales call quality
Criteria
KeyLabelMaxWeight
greeting
Warm greeting
5
1
×
discovery
Needs discovery
10
2
×
compliance
Disclosure read
5
3
×
+ Criterion Save scorecard
1
2
3
4
5
6
  1. Name & Description — the rubric’s title and an optional note for reviewers.
  2. Key — the stable machine key; left blank, it is auto-derived from the label.
  3. Max — maximum points for that criterion (must be > 0).
  4. Weight — relative importance; 2 and 3 here make discovery and compliance count more.
  5. + Criterion — appends another builder row.
  6. Save scorecard — validates and persists the rubric, then auto-selects it.
The New scorecard form with the criterion builder.

19.3.2 Editing and retiring

Scorecards are partially updatable: an admin can change the name, description, criteria or active flag without touching the rest. Rather than deleting a rubric you have outgrown, switch its active flag off — it becomes a retired card that still renders for historical evaluations but signals it should no longer be used for new scoring. Deleting a scorecard outright is allowed, and is safe: existing evaluations keep their numbers because the link is severed rather than cascaded.

Edit criteria, not history

Because every evaluation snapshots its own total and maximum, changing a live scorecard’s weights does not re-score past evaluations — old rows keep the numbers they were saved with. That is intentional, but it means two evaluations on the “same” card can use different weightings. When you materially change a rubric, prefer retiring it and creating a new version so reports stay comparable within a version.

CloudCX · Administrator & Training ManualCh. 19 · Quality — scorecards & evaluations
CloudCXCloudCX Administrator & Training Manual
Ch. 19 · Quality

19.4 Evaluating an interaction

The Evaluate interaction panel is where scoring happens. It has two inputs at the top — a picker and a manual field for choosing which interaction — and, once a scorecard is selected, a row of scoring inputs with a live running total.

19.4.1 Picking the interaction

Selecting a scorecard on the left and an interaction on the right is all it takes to reveal the scoring inputs. Until both are chosen the panel shows the hint // select a scorecard above and an interaction to begin scoring.

supervisor.cloudcx.app

Evaluate interaction

Voice — Sales QA
Interaction (recent threads)
Refund request · whatsapp · 7f3a9c20
…or thread ID
uuid
Running total 23 / 30 77%
Warm greeting 5 / 5
Needs discovery 7 / 10
Disclosure read 5 / 5
Reviewer notes
Optional comments…
✨ AI score Save evaluation
1
2
3
4
5
6
7
  1. Interaction picker — recent omni threads; the chosen one is scored.
  2. Thread ID — paste an exact UUID instead; the picker clears it when used.
  3. Running total — the live weighted score and percentage, recomputed on every keystroke.
  4. Per-criterion input — award 0…max points (half-point steps); shown as n / max.
  5. Reviewer notes — optional free-text saved with the evaluation.
  6. AI score — asks CloudCX AI to score every criterion (§ 19.5).
  7. Save evaluation — records the score against this interaction.
The Evaluate interaction panel, mid-review at 77%.
The running total is just a preview

The percentage that turns green / amber / red as you type is computed in the browser from the same clamp-and-weight rule. The authoritative numbers are recomputed on the server when you press Save, so the saved score will match what you see — even if a criterion’s max changed underneath you.

CloudCX · Administrator & Training ManualCh. 19 · Quality — scorecards & evaluations
CloudCXCloudCX Administrator & Training Manual
Ch. 19 · Quality

19.4.2 Scoring by hand

  1. Select the scorecard. Click its card on the left; a pink ring confirms the selection and its criteria load on the right.
  2. Pick the interaction. Choose a recent thread from the dropdown, or paste a thread UUID into the ID field.
  3. Award points. For each criterion, type the points earned (0 up to the criterion’s max, in half-point steps). Watch the Running total and percentage update as you go.
  4. Add notes. Use the Reviewer notes box to record coaching points, e.g. “Strong discovery; missed the upsell at close.”
  5. Save. Click Save evaluation. The server recomputes the weighted total, attributes the agent (defaulting to the thread’s assignee) and you as the reviewer, and the row drops into Past evaluations with a toast confirming the percentage.

The agent being scored is resolved automatically: if you do not name one, CloudCX attributes the score to the thread’s assigned agent. The reviewer is always the signed-in user. Both are stored on the evaluation so a coaching report can be filtered by agent or by who reviewed them.

19.4.3 Reading the colour bands

Percentages are colour-coded identically everywhere — on the running total, on the AI toast, and on the score tiles in Past evaluations — so a glance tells you the verdict:

Table 19.1 — Score colour bands
BandRangeColourReading
Pass≥ 80% GreenMeets the quality bar; light-touch or no coaching.
Watch60–79% AmberAcceptable but with clear gaps; schedule coaching.
Fail< 60% RedBelow standard; prioritise review and follow-up.
No scoremax = 0 GreyNothing to score (empty rubric); percentage is blank.
Calibrate the team

The bands are fixed in the UI, but your definition of an 80% is set by how you weight criteria. Before rolling QA out, have two or three analysts score the same five interactions and compare — if their percentages diverge by more than ~10 points, your criteria need sharper definitions or your weights need rebalancing. This calibration step is the single biggest driver of fair, trusted scores.

19.4.4 Scoring a voice call

The Supervisor UI scores omnichannel threads, but the underlying model can also pin an evaluation to a voice call by its CloudCX Switch call_uuid (the same identifier you see on a CDR). This lets a future call-recording review attach a score to a specific call leg. For voice today, score the omni thread the call belongs to, or note the call UUID in the reviewer notes for traceability.

CloudCX · Administrator & Training ManualCh. 19 · Quality — scorecards & evaluations
CloudCXCloudCX Administrator & Training Manual
Ch. 19 · Quality

19.5 AI auto-scoring

Manual review is thorough but slow. The ✨ AI score button hands the interaction to CloudCX AI, which reads the conversation and proposes a score for every criterion on the selected scorecard, with a one-line rationale for each. You stay in control: the AI’s scores land in the same editable inputs, so you review, adjust and save them as a human-reviewed evaluation.

19.5.1 What happens under the hood

The AI auto-score pipeline
1 · Load transcriptEvery message on the thread is pulled in order and rendered as a plain text transcript — inbound lines become Customer:, outbound become Agent:.OmniMessage
2 · Build the rubric promptEach criterion (key, label and max) is listed in a system prompt that instructs the model to score strictly from the transcript.Scorecard
3 · Ask CloudCX AIThe transcript + rubric go to the shared platform AI. The model returns strict JSON: {key: {points, rationale}} for every criterion.CloudCX AI
4 · Clamp & totalPoints are clamped to each max, weighted, and rolled up exactly like a manual score.compute_totals
5 · Persist as draftAn evaluation is saved with ai_generated = true; the per-criterion rationales come back to the UI (they are shown, not stored).Evaluation

The result: pressing AI score immediately writes an ✨ AI-badged evaluation to Past evaluations, and fills the scoring inputs with the AI’s numbers plus a ✨ rationale under each criterion. A note appears reminding you that this was saved as a draft — edit the scores if you disagree and press Save evaluation to record your own reviewed version alongside it.

supervisor.cloudcx.app

Evaluate interaction

✨ AI scored · review & edit
Running total 21 / 30 70%
Warm greeting 5 / 5
✨ Greeted by name and gave their team in the first line.
Needs discovery 6 / 10
✨ Asked two needs questions but jumped to pricing early.
// note: the AI score was saved as a draft evaluation — edit + Save to record a reviewed score
✨ AI score Save evaluation
1
2
3
4
  1. AI-scored banner — confirms CloudCX AI has scored and the numbers are now editable.
  2. Editable AI score — the model’s points land in the same inputs; change any you disagree with.
  3. ✨ Rationale — the model’s one-line justification, shown under each criterion (not stored).
  4. Draft note — reminds you the AI row was saved as a draft; Save to record your reviewed score.
An AI-scored interaction with editable points and per-criterion rationales.
CloudCX · Administrator & Training ManualCh. 19 · Quality — scorecards & evaluations
CloudCXCloudCX Administrator & Training Manual
Ch. 19 · Quality

19.5.2 Running an AI score

  1. Select a scorecard and an interaction. The AI button needs both; it will toast you if either is missing.
  2. Press ✨ AI score. The button shows ✨ Scoring… while CloudCX AI reads the transcript and scores the rubric.
  3. Read the rationales. Each criterion now shows the AI’s points and a ✨ rationale. The toast reports the AI’s overall percentage.
  4. Adjust where you disagree. Override any points; the running total and percentage recompute live. The rationales stay visible to argue against.
  5. Save your reviewed score. Add reviewer notes if useful and press Save evaluation. This records a human-reviewed evaluation; the AI’s original draft remains in the list for audit.

19.5.3 Prerequisites & limits

Table 19.2 — AI auto-score outcomes
OutcomeWhat you seeWhat to do
Scored OKInputs fill, ✨ rationales appear, toast shows the %Review, edit, Save.
AI not configuredToast: ✨ AI not configured — set the platform AI key in adminAsk a platform admin to set the AI credentials.
Empty thread / rubricError toast (the interaction has no messages, or the card no criteria)Pick a thread with messages; add criteria to the card.
Model unusableToast: AI score failedRetry; if it persists, score by hand and flag the AI config.
The AI proposes; the human decides

An AI score is a first draft, not a verdict. It can miss sarcasm, business context, or a policy your team enforces. Never let an unreviewed AI evaluation drive coaching, pay or discipline — always open it, sanity-check the rationales against the transcript, correct the points, and save your own reviewed score. The ✨ AI badge in Past evaluations exists precisely so reviewed and unreviewed scores never get confused.

CloudCX · Administrator & Training ManualCh. 19 · Quality — scorecards & evaluations
CloudCXCloudCX Administrator & Training Manual
Ch. 19 · Quality

19.6 Past evaluations

Every saved score — manual or AI — appears in the Past evaluations panel beneath the QA grid, newest first. When an interaction is selected the list filters to that thread; otherwise it shows the recent evaluations across your tenant. Each row carries a colour-coded percentage tile, the scorecard name, an ✨ AI badge for auto-scored rows, the timestamp and short thread id, any reviewer notes, and the raw total / max.

supervisor.cloudcx.app

Past evaluations

// thread 7f3a9c20
87%
Voice — Sales QA
15 Jun 2026, 10:42 · 7f3a9c20
Strong discovery; missed the upsell at close.
26 / 30
70%
Voice — Sales QA ✨ AI
15 Jun 2026, 10:39 · 7f3a9c20
21 / 30
52%
Chat — Support QA
14 Jun 2026, 16:08 · 3b1e77aa
13 / 25
1
2
3
4
  1. Filter indicator — shows the list is filtered to the selected thread (or // recent otherwise).
  2. Score tile — the percentage, colour-banded green / amber / red.
  3. ✨ AI badge — marks an auto-scored (draft) evaluation, distinct from the reviewed 87% above it.
  4. Raw total — the saved weighted total / max behind the percentage.
Past evaluations for one interaction — a reviewed 87% above the AI’s 70% draft.

19.7 Worked example — reviewing a sales call thread

Let’s score one interaction end-to-end against the Voice — Sales QA card built earlier. That card has three criteria: Warm greeting (max 5, weight 1), Needs discovery (max 10, weight 2), and Disclosure read (max 5, weight 3).

  1. Select the card. Click Voice — Sales QA in the scorecards list; the three criteria load on the right.
  2. Pick the interaction. Choose Refund request · whatsapp · 7f3a9c20 from the dropdown.
  3. Let the AI take a first pass. Press ✨ AI score. CloudCX AI returns Greeting 5/5, Discovery 6/10 (“jumped to pricing early”), Disclosure 5/5 — a draft of 70%.
  4. Apply judgement. Re-reading the transcript you agree the agent did probe needs well before pricing, so you raise Discovery to 7. Greeting and Disclosure stand.
  5. Add a note. In Reviewer notes: “Strong discovery; missed the upsell at close.”
  6. Save. Press Save evaluation. The reviewed row records 26/30 = 87%, sitting above the AI’s 70% draft in Past evaluations.
Warm greeting
5 / 5 × 1 = 5
Needs discovery
7 / 10 × 2 = 14
Disclosure read
5 / 5 × 3 = 15
Weighted total
34 / 40 = 85%
Why the maths matters

Note how weighting changes the verdict. Disclosure read (weight 3) contributes the most to both the earned total and the maximum — a compliance miss there would sink the score far faster than fumbling the greeting. The weighted denominator here is 5×1 + 10×2 + 5×3 = 40, so 34 earned points lands at 85% — comfortably in the green band. Tune your weights so the criteria you cannot afford to fail dominate the score.

CloudCX · Administrator & Training ManualCh. 19 · Quality — scorecards & evaluations
CloudCXCloudCX Administrator & Training Manual
Ch. 19 · Quality

19.8 Training track

Try it — build a card, AI-score, then review

In a non-production tenant, complete the full QA loop:

  1. Create a scorecard Chat — Support QA (training) with four criteria: greeting (max 5, weight 1), understanding (max 10, weight 2), resolution (max 10, weight 3) and tone (max 5, weight 1). Leave one Key blank and confirm it auto-slugs from the label.
  2. Save the card and confirm it is active with 4 criteria and that it auto-selects.
  3. Pick any recent chat thread, press ✨ AI score, and read each ✨ rationale. Note the AI’s overall percentage from the toast.
  4. Disagree with at least one criterion on purpose — change its points and watch the running total recolour across a band boundary (e.g. push it from amber up past 80% to green).
  5. Add a reviewer note and Save evaluation. Confirm two rows now sit in Past evaluations: your reviewed score and the AI’s ✨-badged draft.
  6. Finally, retire the training card (set it inactive) and confirm the historical evaluations still render with their numbers intact.

Check yourself: Compute the maximum by hand — 5×1 + 10×2 + 10×3 + 5×1 = 65. Does the / max on your running total read / 65? If not, re-check a weight.

Make QA a habit, not an event

Sample consistently rather than only reviewing complaints — e.g. two interactions per agent per week, plus any flagged by a low CSAT survey (Chapter 20). Use AI to triage volume, then human-review the borderline and the failing ones. Over a month, the trend per agent matters more than any single score.

19.9 Chapter recap

Quality scoring turns interactions into a measurable, coachable signal. Pair it with the customer-survey results in the next chapter to see both sides of the conversation — how well the agent handled it, and how the customer felt about it.

CloudCX · Administrator & Training ManualCh. 19 · Quality — scorecards & evaluations
CloudCXCloudCX Administrator & Training Manual
Ch. 20 · Surveys
Chapter 20

Surveys — CSAT and NPS

A conversation does not end when an agent clicks Resolve. The last word belongs to the customer — “how did we do?” CloudCX answers that with a lightweight post-interaction survey engine: you author a reusable template (a CSAT star rating, an NPS 0–10 question, or a custom mix), send an invite for a finished interaction, the customer fills in a clean branded page at cloudcx.app/survey, and the score flows straight into the Reports dashboard. This chapter takes you from an empty template list to a live CSAT number, end to end.

What you will learn

The three moving parts — template, invite and response — and how they relate; how to author a CSAT and an NPS template, question by question; how an invite mints an unguessable token and turns it into a public link you hand to the customer; how the public survey page behaves in each of its states; how the platform derives the single score from a response; how CSAT average and NPS are calculated and read on the Reports dashboard; plus a fully worked CSAT survey and a Try-it exercise you can reproduce in a test tenant.

Surveys sit downstream of everything you built in the channel and routing chapters. Chapter 16 — The unified inbox gave every conversation a single channel-agnostic thread; a survey invite simply points at one of those threads (optionally) and asks its customer for a rating once the work is done. The result lands on the same Reports view introduced in Chapter 19 — Analytics & reporting, alongside handle time and volume. You administer the templates and the sending policy; the scoring and aggregation run for you on the server.

20.1 Three parts: template, invite, response

The whole engine is three records that mirror the ACD and omni patterns you already know. Keeping them straight is the key to everything that follows, so meet them once, clearly:

The survey lifecycle — author once, send per interaction, aggregate many
Templateauthor once
Sendmint token
Invitepending · /survey/?t=
Customerpublic page
Responseanswers + score
ResultsCSAT / NPS

Two design choices shape how you work with these. First, submission is anonymous and token-only: the customer never logs in — the token in the link is the entire capability, exactly the same trust model as the web-chat thread_id from Chapter 17. Second, the score is derived, not asked: CloudCX inspects the answers and picks one number to report on, so your dashboard always has a single comparable figure no matter how many questions a template carries. Section 20.6 explains precisely which number it picks.

Templates · list / create
/api/v1/surveys/templates
Send an invite
POST /surveys/send
Public render / submit
/surveys/public/{token}
Aggregate results
GET /surveys/results
Where surveys live in the console

There is no top-level “Surveys” nav item. Template authoring and sending sit alongside the conversation tooling, and the results surface as the CSAT widget on OperateReports. Throughout this chapter we show the Reports view as the home for survey outcomes; the public page is a standalone site at cloudcx.app/survey that customers reach by link, never through the admin console.

CloudCX · Administrator & Training ManualCh. 20 · Surveys
CloudCXCloudCX Administrator & Training Manual
Ch. 20 · Surveys

20.2 Survey templates

A template is the questionnaire you reuse across thousands of interactions. Get it right once and every invite inherits it. The template list is your starting point — it shows each questionnaire you (or, for a platform admin, every tenant) own, its kind, whether it is active, and when it was created. Inactive templates are kept for history but should not be sent.

20.2.1 The template list

admin.cloudcx.app
Operate
Dashboard
ReportsCSAT
Tenancy
Resellers
Tenants
Platform
Routing
Channels
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Survey templates// 3 templates · 2 active
PRODAO
Reports · Feedback

Survey templates

Reusable CSAT, NPS and custom questionnaires.

+ New template
NameKindQuestionsStatusCreated
CSPost-chat CSAT csat3 Active12 Jun 2026
RNRelationship NPS nps2 Active02 Jun 2026
VCVoice callback CSAT csat2 Inactive20 May 2026
1
2
3
4
5
  1. Reports — the Operate-group view that hosts feedback. Survey outcomes appear in the CSAT widget here.
  2. New template — opens the create-template form (Section 20.2.2).
  3. Name — your label for the questionnaire, e.g. Post-chat CSAT. Click a row to open and edit it.
  4. Kindcsat, nps or custom. This drives how the score is read and whether NPS is computed.
  5. StatusActive templates may be sent; Inactive ones are retained for history only.
The survey template list, reached from Reports.

Templates are tenant-scoped. A tenant administrator sees and creates only their own tenant’s templates; a platform administrator (no tenant) can create a template with no owner — a platform-default questionnaire — or stamp it onto a specific tenant. This mirrors the queues-and-skills ownership model from Chapter 14: leave the owner blank for a shared default, or pin it to a tenant for that tenant alone.

20.2.2 Creating a template

A template is little more than a name, a kind, and an ordered list of questions. Each question is a small record — a stable key, a human prompt, a type, and (for ratings) a scale. The three question types are the whole vocabulary:

Table 20.1 — The three survey question types
TypeRenders asAnswer valuescaleCounts toward score?
ratingA row of stars, 1…scaleInteger 1–scale2–10, default 5Yes — fallback score
npsAn 0–10 button gridInteger 0–10n/a (fixed 0–10)Yes — primary score
textA free-form comment boxString (optional)n/aNo — verbatim only
The question key is permanent

The key (for example overall or recommend) is how an answer is stored and how the score is found — it is not shown to the customer; the prompt is. Keep keys short, lowercase and stable: changing a key on a live template orphans the answers already collected under the old key. Change the prompt freely; leave the key alone.

CloudCX · Administrator & Training ManualCh. 20 · Surveys
CloudCXCloudCX Administrator & Training Manual
Ch. 20 · Surveys
admin.cloudcx.app
Operate
ReportsCSAT
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
New template// survey · csat
PRODAO
Reports · Feedback

New survey template

Template

NamePost-chat CSAT
Kindcsat
Active   Sendable
Thanks messageShown on the public page after a successful submit.

Questions

+ Add question
Keyoverall
Typerating
Scale5
PromptHow would you rate the support you received?
CancelCreate template
1
2
3
4
5
6
7
  1. Name — the template label (1–255 characters), shown in the list and as the public page heading.
  2. Kindcsat / nps / custom. Pick nps to make the Reports view compute a Net Promoter Score.
  3. Active — a toggle; only active templates are sendable. New templates default to active.
  4. Thanks message — optional copy (≤ 2000 chars) shown after submit. Falls back to a friendly default if blank.
  5. Add question — appends another question row. Questions render top-to-bottom in this order.
  6. Key / Type / Scale — the question’s stable id, its kind, and (rating only) the star count.
  7. Prompt — the wording the customer actually sees above the stars / buttons / box.
Creating a CSAT template with one rating question.

20.2.3 Field reference

Namename
Required, 1–255 characters. Used in the list and as the default heading on the public page.
Kindkind
csat (default), nps or custom. Only nps templates yield an NPS figure in results; CSAT average is computed for all kinds.
Questionsquestions[]
Ordered list of {key, prompt, type, scale?}. type is one of rating/nps/text; scale (2–10, default 5) applies to rating only and is dropped for other types.
Activeactive
Boolean, default true. Inactive templates are kept but should not be sent.
Thanks messagethanks_message
Optional, ≤ 2000 characters. Confirmation copy on the public page after submit (and when re-opening a completed link).
Ownertenant_id
The owning tenant. Tenant admins are pinned to their own tenant automatically; a platform admin may leave it blank (a shared default) or set it.

20.2.4 Author a template — steps

  1. Open Reports → New template. From OperateReports, choose New template to open the form.
  2. Name it for its use. Type a clear name such as Post-chat CSAT or Relationship NPS — you will pick it by name when sending.
  3. Choose the kind. Select csat for a satisfaction rating or nps for a 0–10 recommendation question. Use custom for a mixed questionnaire where the first numeric question still becomes the score.
  4. Add your first question. Give it a short key (e.g. overall), choose its type, set the scale if it is a rating, and write the customer-facing prompt.
  5. Add a comment question (recommended). Append a text question (key comment) so customers can explain a low score. Text answers never affect the number.
  6. Write the thanks message. Keep it warm and short; it is the last thing the customer sees.
  7. Leave it active and Create. Confirm the Active toggle is on, then Create template. It now appears in the list, ready to send.
Put the scoring question first

Order matters for the derived score. The platform reads the first NPS question, or failing that the first rating question, as the number to report (Section 20.6). Lead with the question you want to track — the headline rating or recommendation — and place follow-up ratings and comments after it.

CloudCX · Administrator & Training ManualCh. 20 · Surveys
CloudCXCloudCX Administrator & Training Manual
Ch. 20 · Surveys

20.3 Sending and triggering a survey

A template is inert until you send it. Sending creates exactly one invite: the platform mints a fresh, unguessable token — a 32-character hex string, collision-safe and impossible to enumerate — records which channel it was delivered over and (optionally) which interaction it belongs to, and hands you back a ready-made public link of the form:

# the survey_url returned by POST /surveys/send
https://cloudcx.app/survey/?t=8f1c2e6a9b7d4f03a1e5c8b20d6f4a91

That URL is everything the customer needs. Where it goes next is your call. The channel field on the invite records how you delivered it — webchat (the default), email, sms, and so on — so you can later compare response rates by channel. The link itself is channel-neutral; channel is just a label on the invite.

20.3.1 The two ways a survey gets sent

Tied to an interaction

Pass a thread_id when sending so the invite (and its eventual response) is attached to the exact conversation that prompted it. This is the normal post-interaction case — the agent resolves a chat, and a survey for that thread goes out.

Standalone

Omit thread_id to send a survey not bound to any single conversation — a relationship NPS blast, or a manual one-off. The flow is identical; the invite simply has no thread to point at.

From a resolved interaction to a link in the customer's hands
Resolvethread closed
Sendtemplate + thread
Token minteduuid4 hex
Deliver linkchat · email · SMS
Invitepending

20.3.2 The send dialog

Send survey

×
TemplatePost-chat CSAT · csat
Interaction (optional)Thread #A7F3 · WhatsApp · Acme Pte LtdLeave blank to send a standalone survey.
Delivery channelwebchat
CancelSend survey
1
2
3
4
  1. Template — the questionnaire to issue. Only active templates you can access appear here.
  2. Interaction — the optional thread_id. Pre-filled when you send from an open conversation; blank for a standalone survey.
  3. Delivery channel — the channel stamped on the invite (default webchat); a label for reporting, not the transport.
  4. Send survey — mints the invite + token and returns the public link to copy or auto-deliver.
The send-survey dialog, tied to a WhatsApp thread.

20.3.3 Send a survey — steps

  1. Finish the interaction. Resolve the chat, call or email so the conversation is complete — surveys are post-interaction.
  2. Open the send dialog. Choose Send survey (from the interaction’s wrap-up actions, or as a standalone action) to open the dialog above.
  3. Pick the template. Select the questionnaire — e.g. Post-chat CSAT. It must be active and within your tenant.
  4. Confirm or clear the interaction. Keep the pre-filled thread to bind the response to this conversation, or clear it for a standalone send.
  5. Set the delivery channel. Choose how you are delivering the link (webchat, email, sms…). This only labels the invite.
  6. Send and deliver the link. Confirm. CloudCX returns the survey_url; post it into the chat, email it, or text it. The invite is now pending.
One token, one response

Each send creates a new token. Sending the same template to the same customer twice issues two independent invites — and two links. Don’t loop the send action expecting it to be idempotent; one resolved interaction normally warrants exactly one invite. A token can be submitted only once (Section 20.5).

CloudCX · Administrator & Training ManualCh. 20 · Surveys
CloudCXCloudCX Administrator & Training Manual
Ch. 20 · Surveys

20.4 The public survey page

When the customer opens the link, they land on a small, fast, fully branded page at cloudcx.app/survey — no login, no account, no console. The page reads the token from the ?t= query parameter, fetches the template’s questions, and renders them. It has four states, and you should know all four because they map directly to what a customer can experience:

Loading

A brief spinner while the page fetches the survey for its token.

Survey

The questionnaire: each question with stars, an NPS grid or a comment box, and a Submit feedback button.

Thank you

The confirmation, showing your thanks message. Also shown if the link was already used.

Invalid

A friendly “this link isn’t available” when the token is missing, wrong or no longer active.

cloudcx.app/survey/?t=8f1c…4a91
CloudCXCX Feedback
Post-conversation survey

How did we do?

Your feedback takes less than a minute and helps us take every conversation beyond.

Q01
How would you rate the support you received?
Good
Q02
Anything we could have done better?
Tell us more (optional)…
Submit feedback  →
Powered by CloudCX · Omnichannel CX
1
2
3
4
5
  1. Heading — the template’s name (falling back to “How did we do?”), under a small “Post-conversation survey” eyebrow.
  2. Rating question — 1–5 stars by default. Four selected here reads as Good; the 5-point scale shows word labels Very poor → Excellent.
  3. Live rating label — the word (or n / scale for non-5 scales) updates as the customer taps a star.
  4. Comment box — a text question; always optional.
  5. Submit feedback — posts the answers for this token; on success the page swaps to the Thank-you state.
The public survey page rendering a CSAT template (rating + comment).

An NPS question renders differently: a row of eleven buttons numbered 0 to 10, captioned Not likely on the left and Very likely on the right. The customer taps one; that integer is the answer. Everything else — the branded card, the gradient bar, the thanks state — is identical.

CloudCX · Administrator & Training ManualCh. 20 · Surveys
CloudCXCloudCX Administrator & Training Manual
Ch. 20 · Surveys

20.4.1 The thank-you and invalid states

Thank you!

Your feedback helps us take every conversation beyond.

Thank-you state, showing the template’s thanks message.
!

This survey link isn’t available

The link may have expired or already been used.

Invalid state for a missing or unknown token.

Two behaviours are worth committing to memory because customers will hit them:

20.4.2 Validation: what must be answered

The page enforces one simple rule before it will submit: every non-text question needs an answer. Ratings and NPS questions are required; text comment questions are always optional. If the customer taps Submit with a rating unanswered, the page points them at the first missing question (“Please answer question 1 before submitting”) and does not send. This keeps your score data complete — you never get a “response” with no rating in it.

Accessibility and reduced motion

The page is keyboard- and screen-reader-friendly: stars and NPS buttons carry aria-labels (“4 of 5”, “9 out of 10”) and the comment box is labelled by its prompt. It also honours prefers-reduced-motion, dropping the hover/transition animations. You don’t configure any of this — it is built into the public page — but it is good to be able to tell a customer the survey meets their needs.

20.5 What happens on submit

When the customer presses Submit feedback, the page posts their answers for the token. Server-side, three things happen atomically: a Response is recorded with the full answer map, the platform derives and stores the score (Section 20.6), and the invite flips from pending to completed. The page then shows the thanks state.

Submission is protected and single-use:

CloudCX · Administrator & Training ManualCh. 20 · Surveys
CloudCXCloudCX Administrator & Training Manual
Ch. 20 · Surveys

20.6 How the score is derived

Every response stores the customer’s full answers, but reporting needs one comparable number per response. CloudCX derives that score with a deliberately simple, predictable rule applied to the template’s question list:

1 · First NPS questionIf the template has an nps question, its answer (0–10) becomes the score.primary
2 · First rating questionOtherwise, the first rating question’s answer (1–scale) becomes the score.fallback
3 · Neither presentA text-only template has no numeric score; the response is stored with score = null.null

This is why question order matters and why the “scoring question first” tip in Section 20.2.4 is more than style. A CSAT template’s headline star rating becomes the score; an NPS template’s 0–10 question becomes the score — even if a rating question also exists, NPS wins because it is checked first. The value is coerced to a number defensively (a numeric string still counts; a non-numeric answer yields no score), so the dashboard is never polluted by junk.

One score per response, by design

You can ask a customer three questions, but exactly one of them drives CSAT/NPS. Use additional ratings for diagnostic detail (they are stored in answers and visible in the histogram only if they share the same value buckets) and text for verbatims. Keep the metric you actually report on as the first numeric question.

20.7 Reading results: CSAT and NPS

Aggregate results are computed on demand for one template over an optional date window, and surfaced as the CSAT widget on OperateReports. The results payload has a small, stable shape:

Table 20.2 — The results payload (GET /surveys/results)
FieldMeaningWhen present
csat_avgMean of all numeric scores in the window (2 dp)Any kind, when there is at least one scored response; else null.
npsNet Promoter Score, −100…+100Only when the template kind is nps and there are scores; else null.
responsesCount of responses counted in the windowAlways (0 if none).
breakdownCount of responses per integer score bucketAlways; e.g. {"5":12,"4":6,"3":2}.

20.7.1 How NPS is calculated

For an NPS template, CloudCX applies the standard rule to the 0–10 scores: respondents scoring 9–10 are promoters, 0–6 are detractors, and 7–8 are passives (counted in the base but neither for nor against). The score is the percentage of promoters minus the percentage of detractors:

# NPS, computed only for kind == nps
NPS = (%promoters%detractors)        # range −100 … +100

# worked: 50 responses → 30 promoters (9–10), 5 detractors (0–6), 15 passives (7–8)
NPS = (30/50 − 5/50) × 100 = (60% − 10%) = +50.0

CSAT, by contrast, is simply the mean of the stored scores — for a 5-star template that is an average like 4.6 out of 5. The Reports widget normalises whatever it receives to a single ring: a 0–5 average is shown as a one-decimal figure, and where present the NPS and per-score breakdown are listed beside it. If there are no responses in the window yet, the widget shows a friendly empty note rather than a zero.

CloudCX · Administrator & Training ManualCh. 20 · Surveys
CloudCXCloudCX Administrator & Training Manual
Ch. 20 · Surveys
admin.cloudcx.app
Operate
Dashboard
ReportsCSAT
Platform
Routing
Channels
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Reports// last 30 days
PRODAO
Operate · Feedback

Survey results

Post-chat CSAT · 1–30 Jun 2026

Live
CSAT avg
4.6
/ 5 · ▲ 0.2
Responses
128
in window
NPS
csat template
Response rate
38%
128 / 337 sent

Score breakdown

/surveys/results
ScoreResponsesShare
5 ★78 61%
4 ★32 25%
3 ★11 9%
2 ★5 4%
1 ★2 1%
1
2
3
4
5
  1. Window & Live — results are computed over an optional from/to date range; here, the last 30 days.
  2. CSAT avg — the mean score (csat_avg), shown out of 5 for a star template.
  3. NPS — dashed here because this is a CSAT template; it would carry a number only for an nps template.
  4. Source — the widget reads /api/v1/surveys/results; if the service has no data it degrades to a friendly empty note.
  5. Breakdown — the per-score histogram (breakdown), one row per integer bucket, newest counts live.
The Reports CSAT widget reading aggregate survey results.

20.7.2 Read results — steps

  1. Open Reports. Go to OperateReports; the CSAT widget loads survey results for the active template.
  2. Choose the template and window. Results are per-template; set a from/to range (e.g. this month) to scope the figures.
  3. Read the headline. Check CSAT avg and Responses; for an NPS template, read the NPS figure (−100…+100).
  4. Inspect the breakdown. Use the per-score histogram to see whether a good average hides a tail of 1–2 ★ detractors.
  5. Act on the verbatims. Pair low scores with their text comments (stored on each response) to find the “why” behind the number.
CloudCX · Administrator & Training ManualCh. 20 · Surveys
CloudCXCloudCX Administrator & Training Manual
Ch. 20 · Surveys

20.8 Worked example — a post-chat CSAT, end to end

Let’s run the whole lifecycle once, concretely. Acme Pte Ltd wants a 5-star CSAT survey after every resolved web chat, with a comment box for context. We will author it, send one for a real thread, watch the customer answer, and read the result.

20.8.1 Author the template

From OperateReportsNew template we create a csat template named Post-chat CSAT with two questions and a thanks message. Conceptually it is:

# POST /api/v1/surveys/templates  (what the form sends)
{
  "name": "Post-chat CSAT",
  "kind": "csat",
  "active": true,
  "thanks_message": "Thank you! Your feedback helps us take every conversation beyond.",
  "questions": [
    { "key": "overall", "type": "rating", "scale": 5,
      "prompt": "How would you rate the support you received?" },
    { "key": "comment", "type": "text",
      "prompt": "Anything we could have done better?" }
  ]
}

The first numeric question is the rating keyed overall — that will become the score. The comment question is text, so it never affects the number.

20.8.2 Send it for a resolved chat

An agent resolves chat Thread #A7F3 with a customer at Acme. From the wrap-up actions they Send survey, pick Post-chat CSAT, keep the pre-filled thread, leave the channel as webchat, and confirm. The platform mints an invite and returns:

# POST /surveys/send  →  201 Created
{
  "invite_id": "b2a1…e9",
  "token": "8f1c2e6a9b7d4f03a1e5c8b20d6f4a91",
  "status": "pending",
  "survey_url": "https://cloudcx.app/survey/?t=8f1c2e6a9b7d4f03a1e5c8b20d6f4a91"
}

The agent drops that survey_url into the chat as a closing line: “Glad we could help — would you mind rating this chat? cloudcx.app/survey/?t=8f1c….

20.8.3 The customer answers

The customer opens the link. The page fetches the questions for the token and renders the star row and comment box (exactly the page in Figure 20.5). They tap 4 stars — the live label reads Good — and type “Quick and friendly, but I waited a while to connect.” into the comment box, then press Submit feedback. The page posts:

# POST /surveys/public/8f1c…4a91
{ "answers": { "overall": 4, "comment": "Quick and friendly, but I waited a while to connect." } }

Server-side, a response is recorded, the score is derived from the first numeric question — overall = 4 — so score = 4.0, and the invite flips to completed. The customer sees “Thank you!” with the template’s thanks message. If they click the link again later, they get the thanks state, not a second form.

20.8.4 Read the result

On OperateReports, this response now contributes to Post-chat CSAT. After a month of chats the widget shows a csat_avg of 4.6 / 5 across 128 responses, with a breakdown of {"5":78, "4":32, "3":11, "2":5, "1":2} — our 4-star answer is one of the 32 in the “4” bucket. The comment is stored on the response, so a supervisor reviewing low-and-middling scores can read “I waited a while to connect” and tie a CSAT dip back to queue wait time from Chapter 14.

Turn it into NPS in one change

To track loyalty instead of episode satisfaction, clone this template, set kind to nps, and make the first question an nps type keyed recommend (“How likely are you to recommend Acme to a friend or colleague?”). Send it on a cadence (say quarterly) rather than per chat, and the Reports widget will now populate the NPS figure as well as the average.

CloudCX · Administrator & Training ManualCh. 20 · Surveys
CloudCXCloudCX Administrator & Training Manual
Ch. 20 · Surveys

20.9 Training: build and run your own survey

Try it — a CSAT survey from scratch (≈ 12 minutes)

Goal: author a CSAT template, send one invite, submit a response as the “customer”, and watch the number move on Reports. Use a test tenant.

  1. Create the template. Reports → New template. Name it Training CSAT, kind csat. Add a rating question (key overall, scale 5, prompt “How did we do today?”) and a text question (key comment). Write a thanks message. Create it.
  2. Send a standalone invite. Use Send survey, pick Training CSAT, leave the interaction blank, channel webchat. Copy the returned survey_url.
  3. Open the link. Paste the URL into a fresh browser tab (or your phone). Confirm the page shows your prompt and a 5-star row.
  4. Try to skip the rating. Press Submit with no star selected and confirm the page asks you to answer question 1 first.
  5. Submit a 5. Tap 5 stars (label reads Excellent), add a comment, submit. Confirm you see the Thank-you state with your message.
  6. Re-open the link. Reload the same URL and confirm it shows Thank-you again — not a second form. The token is single-use.
  7. Read the result. On Reports, confirm Training CSAT now shows csat_avg = 5.0, responses = 1, and a breakdown of {"5":1}.

Stretch: clone the template as nps with a single nps question keyed recommend. Send three invites; submit a 10, a 9 and a 5. Predict the NPS before you look: two promoters, one detractor over three responses → (2/3 − 1/3) × 100 = +33.3. Confirm Reports agrees.

20.10 Common pitfalls

Table 20.3 — Survey troubleshooting
SymptomLikely causeResolution
Reports CSAT shows an empty noteNo scored responses in the window yet, or the survey service returned no dataExpected before responses arrive. Widen the date window; confirm invites are actually being submitted.
A response has no scoreThe template has only text questions (no rating/nps)Add a numeric question. Text-only templates collect verbatims but produce score = null.
NPS stays on ReportsThe template kind is csat/custom, not npsNPS is computed only for nps templates. Set the kind to nps (and use an nps question).
Customer sees “link isn’t available”Token mistyped/truncated, or no ?t= on the URLRe-send to mint a fresh token; deliver the full link without trimming the query string.
“Already submitted” on a first attemptThe same token was opened/submitted twice (e.g. double-click)Expected — tokens are single-use. Send a new invite if a genuine re-survey is needed.
Wrong question drives the scoreA rating question precedes the intended NPS, or questions are mis-orderedPut the metric you report on first; NPS always wins over rating when both exist.
Old answers “disappear” after an editA question key was renamed on a live templateNever rename a live key. Edit the prompt instead; renaming orphans previously collected answers.

20.11 Glossary & what’s next

Template
A reusable questionnaire (csat/nps/custom) with an ordered list of {key, prompt, type, scale?} questions, an active flag and a thanks message.
Invitetoken
One issued survey for one interaction, carrying an opaque single-use token that is the entire capability for the public link; status is pending then completed.
Responseanswers + score
A customer’s submitted {key: value} answer map plus the derived numeric score and a submit timestamp.
Score
The single reporting number per response: the first nps answer, else the first rating answer, else null.
CSATcustomer satisfaction
The mean of stored scores (csat_avg); for a 5-star template, an average out of 5.
NPSnet promoter score
%promoters (9–10) − %detractors (0–6), −100…+100; computed only for nps templates.
Public survey page
The standalone, login-free site at cloudcx.app/survey that renders a survey by token and records the response.
What’s next

You can now author, send, collect and read a survey end to end — turning a finished conversation into a CSAT or NPS number on the dashboard. The next chapters widen the lens: Chapter 21 takes survey scores, handle time and volume into deeper analytics and scheduled exports, and the operations chapters cover retention and data-governance for the responses you collect. Keep the “one numeric question, asked first” discipline and your CSAT trend will stay clean and comparable across every channel.

CloudCX · Administrator & Training ManualCh. 20 · Surveys
CloudCXCloudCX Administrator & Training Manual
Ch. 21 · AI
Chapter 21

AI — insights, replies, bots and transcription

CloudCX layers a set of optional, opt-in AI features over the omnichannel platform you have already learned to run. On text conversations, an insights engine reads a thread and returns sentiment, a summary, topics and a suggested reply; agents see this in the AI Assist panel. A per-tenant chatbot can auto-answer inbound web chat, WhatsApp, SMS and email and hand off to a human when it should. On voice calls, a transcription pipeline turns a recording into searchable text and then runs the same insight model over it for an after-call sentiment and summary, and a text-to-speech backend synthesises announcements. Every one of these is dark until you turn it on by storing a provider key in the encrypted credential vault. This chapter shows exactly where each feature lives, what it does, and how to enable it safely.

What you will learn

Which AI features exist and which provider powers each; how to store the shared AI key (and the STT/TTS keys) in Platform Credentials; how the agent AI Assist panel produces sentiment, summary, topics and a suggested reply, and how that result is cached; how to enable, scope and tune the opt-in chatbot per tenant, and how it decides to reply or hand off; how call transcription runs automatically on hang-up and on demand, and where the transcript and its AI fields surface; and the platform’s fail-safe contract — with no key set, nothing breaks and every conversation routes to a human exactly as before.

A single principle ties the whole chapter together, so internalise it now: AI is additive and fail-safe. With no credentials configured, the insight routes return a clean 503, the chatbot never touches an inbound message, and transcription is a silent no-op — the platform deploys and runs identically to one with no AI at all. You enable a feature by giving it a key; you disable it by removing the key. There is no big red switch and no half-on state that can drop a customer.

21.1 The AI landscape at a glance

It helps to see all five capabilities, the provider each one uses, and the credential it reads, before drilling in. CloudCX deliberately splits the providers: text intelligence is CloudCX’s CloudCX AI; speech is a dedicated speech vendor (CloudCX Speech by default). They are configured independently — you can run insights without ever touching voice, or transcription without a chatbot.

Table 21.1 — AI features, the provider behind each, and the vault credential it reads
FeatureWhat it doesProviderVault credentialSurfaces in
Insights & sentimentSentiment, score, summary, topics, suggested reply over a text threadCloudCX AIaiAgent AI Assist panel
Suggested repliesOne drafted next agent reply, in a chosen toneCloudCX AIaiAgent AI Assist panel
ChatbotAuto-answers inbound text and hands off to a human (opt-in per tenant)CloudCX AIaiDigital channels · Bot config
Call transcriptionSpeech-to-text of a recording, then CloudCX AI sentiment + summaryCloudCX Speech + CloudCX AIstt (+ ai)Call record / transcript
Text-to-speech (TTS)Synthesises spoken audio for announcements / testingCloudCX SpeechttsVoice flows · API

Three of the five — insights, suggested replies and the chatbot — share the single ai credential (one CloudCX AI key powers all text intelligence). Transcription needs stt to do the speech part and optionally uses the same ai key for the after-call insight. TTS is fully independent under tts. This matters operationally: store the one CloudCX AI key and three features light up at once.

Where the AI features sit relative to the channels and the credential vault
Agents & customers — AI Assist panel, bot replies, transcript view
Insights · Suggested replies · Chatbot · Transcription · TTS
AI client wrappers — CloudCX AI Messages API · STT · TTS (all never-raise)
Encrypted Platform Credentials vault — ai · stt · tts
Why these providers

Text analysis (sentiment, summarisation, drafting) is exactly what a large language model excels at, so CloudCX uses CloudCX’s CloudCX AI through the official Messages API. Speech-to-text and text-to-speech are a different problem with different vendors, so they run through a small, pluggable speech layer defaulting to CloudCX Speech. Keeping them separate means a voice key and a text key are managed and rotated independently — and a problem with one never disables the other.

21.2 The key lives in the vault

None of these features read a key from an environment variable or a config file. Every provider secret lives in the Platform Credentials store — the same encrypted, admin-only vault you met in the platform-configuration chapter — as Fernet-encrypted JSON in the platform_credentials table. One row per provider; the field values are secret and are never returned by any endpoint, only a configured flag and the field names. The one root secret that stays outside the database is the encryption master key, BYOND_CREDS_KEY, held in the server environment.

You manage these from PlatformPlatform Credentials in the admin console. The page renders one card per provider, each showing a Configured ✓ badge when a row exists and the fields that provider expects. The three cards that concern this chapter are ai, stt and tts.

admin.cloudcx.app
Platform
Channels
Platform Credentials
Security
Settings
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Platform Credentials// connectivity & AI keys · encrypted
PRODAO
Platform

Platform Credentials

CloudCX’s shared connectivity & AI keys. Stored encrypted; values are never shown.

AI

Configured
API keysk-ant-•••••••• (hidden)
Model (optional)(platform default)
Save credentials Remove

STT

Not set
API key
Provider (optional)(default)
Model (optional)nova-2
Save credentials

TTS

Not set
API key
Provider (optional)(default)
Voice (optional)aura-asteria-en
Save credentials
1
2
3
4
5
  1. Platform Credentials nav item — admin-only; this whole page requires a platform-admin account.
  2. AI card · API key — the CloudCX AI key. Required. Once saved, the badge flips to Configured ✓; the value is encrypted and never shown again.
  3. AI card · Model — optional. Leave blank to use the platform default model, or pin a different model id. Use a bare id with no date suffix.
  4. STT card — the speech-to-text key. Uses the CloudCX Speech service. Needed for call transcription.
  5. TTS card — the text-to-speech key, with an optional voice id. Independent of STT.
Platform Credentials — the AI, STT and TTS cards. Saving a key flips its badge to Configured; values are write-only.

21.2.1 Procedure — enable text AI (the CloudCX AI key)

  1. Obtain a CloudCX AI API key. From your CloudCX account, create an API key for the workspace CloudCX should bill against. This single key powers insights, suggested replies and the chatbot.
  2. Open Platform Credentials. Sign in to admin.cloudcx.app as a platform admin and go to PlatformPlatform Credentials.
  3. Fill the AI card. Paste the key into API key. Leave Model blank to accept the platform default model, or enter another bare model id.
  4. Save. Click Save credentials. The server validates the fields, Fernet-encrypts the payload, and stores it. The badge changes to Configured ✓. (If the server is missing its BYOND_CREDS_KEY, the save is refused with a clear error rather than storing plaintext.)
  5. Confirm it is live. Open the agent desktop; the AI Assist panel’s status indicator should read on (it calls /ai/health, which now reports configured: true). You are ready for §21.3.
The key is shared and platform-wide

The ai credential is CloudCX’s shared key — one key for the whole platform, used by every tenant’s AI features. It is not per-reseller or per-tenant. Treat it as a high-value secret: rotating it (save a new value) takes effect immediately with no restart, and removing it disables every AI feature at once. Because the value is write-only, keep your own record of which key is in use.

21.3 Insights and sentiment — the AI Assist panel

When the CloudCX AI key is set, agents gain an AI Assist panel in the right-hand context column of the Agent Desktop, on any text interaction (web chat, WhatsApp, SMS, email). It is a dark, brand-gradient card with a spark mark and a small status indicator. From it an agent can, with one click, get a read on how the conversation is going and a head-start on the next reply.

Two actions drive the panel, each mapping to a backend route that reads the open thread’s messages in order, builds a labelled transcript (Customer / Agent), and asks CloudCX AI for a specific output:

A third route, POST /ai/threads/{id}/suggest-reply, produces a single drafted reply on its own and accepts an optional tone (default professional) — it is the same drafting that the Analyze bundle includes, exposed separately for callers that want only a reply in a chosen tone.

cloudcx.app/agent
AI Assist ON
Analyze Summary
negative −0.62
Summary
Customer is frustrated that a refund promised five days ago has not arrived and is asking for an escalation.
Topics
refund delay escalation
Suggested reply
I’m sorry the refund hasn’t reached you yet, Rosa. I can see it was approved — let me escalate this to our payments team now and get you a firm date today.
Insert
1
2
3
4
5
  1. Status indicator — reads ON when the CloudCX AI key is set (from /ai/health). It shows off, and the buttons are inert, when AI is not enabled.
  2. Analyze / Summary buttonsAnalyze returns the full bundle below; Summary returns only a wrap-up paragraph.
  3. Sentiment pill — colour-coded positive/neutral/negative with the numeric score alongside.
  4. Summary & topics — a one-line gist and up to five topic chips drawn from the conversation.
  5. Suggested reply · Insert — a draft the agent can drop straight into the composer with Insert, then edit and send. The agent is always in control; nothing is sent automatically.
The AI Assist panel after Analyze on a tense refund chat — sentiment, summary, topics and an insertable suggested reply.

21.3.1 How a result is produced (and cached)

When an agent clicks Analyze, the platform does the following, all guarded so a failure never breaks the desktop:

  1. Gate on configuration. If the ai key is not set, the route returns 503 and the panel shows “AI assist not enabled” — not an error.
  2. Load the thread. The thread’s messages are read oldest-first. An unknown thread is 404; a thread with no messages yet is 422 (“no messages to analyze”).
  3. Check the cache. The analyze result is cached in CloudCX Cache under a key that includes the thread’s last-message timestamp, for about five minutes. If nothing has changed since the last analysis, the cached result is returned and the CloudCX AI round-trip is skipped.
  4. Call CloudCX AI. On a cache miss, a labelled transcript (most recent turns) is sent to CloudCX AI with a strict-JSON instruction. The model returns sentiment, score, summary, topics and a suggested reply.
  5. Coerce and cache. The response is parsed defensively, the score is clamped to the −1.0…+1.0 range, and a successful result is written back to the cache. A transient failure is not cached, so the next click retries.
Why the cache is keyed on the last message

Re-analysing the same unchanged thread would waste a model call (and money) and return the same answer. By folding the thread’s last-message time into the cache key, a new customer or agent turn automatically produces a fresh key — so analysis is always current after a reply, but free to click repeatedly in between. This is invisible to the agent; they just see fast, consistent results.

Insights are text-only

The insight routes analyse text threads. Voice calls are handled separately: a call is transcribed first (§21.6), and the resulting transcript text is then run through the same insight model for an after-call sentiment and summary. There is no “analyse a live call” button — the call must end and be transcribed first.

21.4 The chatbot — opt-in auto-responder

The chatbot lets a tenant have inbound text messages answered automatically by CloudCX AI, with a clean hand-off to a human when the bot should not, or cannot, help. It is the most powerful AI feature and the one with the strongest safety rails, because it can reply to customers on its own. The single most important fact about it is this:

The bot is OFF by default — per tenant, opt-in

There is no platform-wide default bot. With the table empty (the state after install), every tenant’s inbound behaviour is completely unchanged — every message routes to a human exactly as before. The bot acts only for a tenant that has explicitly created an enabled configuration naming the channels it should handle. Enabling it is a deliberate, per-tenant decision.

A tenant’s bot is governed by one bot configuration with these fields:

Enabled enabled
Master switch for this tenant. Defaults to off. While off, the bot never touches inbound.
Channels channels
Which digital channels the bot handles, any of webchat, whatsapp, sms, email. A thread on a channel not in the list falls through to normal human routing untouched.
Persona persona
The system instruction prepended to CloudCX AI — tone, scope, what to help with and when to escalate. Left blank, a sensible default persona is used.
Max turns max_turns
How many bot replies a single thread may receive before it is auto-handed to a human. Default 5; range 0–50. Setting it to 0 hands off on the first inbound (a pure “greet-then-route” bot).
Hand-off message handoff_message
What the customer is told when the bot routes them to a person, so they are never left silent. A warm default is provided.

21.4.1 The Bot configuration screen

admin.cloudcx.app
Platform
Channels
Chatbot
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Chatbot// AI auto-responder · per tenant
AI configuredAO
Tenancy

Chatbot — Acme Retail

Auto-answer inbound text and hand off to a human when appropriate.

Enabled

Configuration

Channels
Web chat WhatsApp SMS Email
Persona (system instructions) You are Acme Retail’s friendly support assistant. Help with orders, delivery and returns. Be concise. Escalate billing changes and angry customers to a human. Prepended to every bot turn. Set tone, scope and escalation rules here.
Max turns before hand-off5
Hand-off messageConnecting you with a teammate…
Save bot config
1
2
3
4
5
  1. AI configured pill — reflects whether the shared CloudCX AI key is set. An enabled bot only actually replies when this is green; with no key it stays silent and everything routes to a human.
  2. Enabled toggle — the per-tenant master switch. This is the opt-in.
  3. Channels — tick the digital channels the bot should handle. Unticked channels behave exactly as if the bot did not exist.
  4. Persona — the system prompt prepended to CloudCX AI for this tenant; the place to set voice, scope and escalation rules.
  5. Max turns / hand-off message / Save — the turn budget, the message shown on hand-off, and the action that writes the config.
The per-tenant Bot configuration — opt-in toggle, channel scope, persona, turn budget and hand-off message.

Behind this screen are two routes. GET /bot/config returns the configuration (a platform admin sees the full list across tenants; a tenant-scoped user sees their own, with a default disabled config when none exists yet, so the screen always has something to render). PUT /bot/config creates or replaces a tenant’s config and is restricted to platform admins and resellers; a tenant-scoped user may only write their own tenant. Both responses carry an ai_configured flag so the UI can warn that an enabled bot will not reply until the CloudCX AI key is set.

21.4.2 How the bot decides: reply, or hand off

On every inbound text message, after the message is stored and before the normal human routing, the platform asks the bot whether it wants to take the turn. The bot returns “handled” only when it has fully dealt with the message (sent a reply, or routed to a human with a hand-off note); otherwise the message falls through to the usual human routing — completely unchanged. It falls through, deliberately, in all of these cases:

When the bot does take a turn, it builds the labelled transcript and asks CloudCX AI for a single strict-JSON object with two fields: a reply (the message to the customer) and a handoff boolean. The persona instructs CloudCX AI to set handoff: true when the customer asks for a human, when the request needs an account action or authentication, when the customer is upset or the issue is sensitive, or when it is simply not confident. The platform then acts on that decision:

The bot’s per-message decision — reply, hand off, or fall through
Inbound message stored
Bot enabled for tenant + channel?
Turn budget left? Key set? Not a human’s thread?
Ask CloudCX AI → {reply, handoff}
handoff = false → send reply, keep thread open
handoff = true → route to a human + post hand-off message
any “no” above → fall through to normal human routing

A bot reply is stored as an ordinary outbound message authored by a sentinel sender named “AI Assistant” and delivered over the thread’s channel (a WhatsApp / SMS / email send, or the live web-chat broadcast). Counting those sentinel messages is how the platform tracks how many turns the bot has spent. Because the thread is left open after a bot reply, a human agent can always step in and claim it; the moment an agent (a different sender) replies, the bot is naturally out of the loop. When the bot hands off, it routes the thread to the channel’s default queue and the available-agent picker, then posts the hand-off message so the customer is never left waiting in silence.

21.4.3 Procedure — turn on the chatbot for one tenant

  1. Confirm the CloudCX AI key is set. The bot needs the shared ai credential (§21.2.1). You can save an enabled config without it, but the bot will stay silent until the key exists.
  2. Open the tenant’s Bot configuration. As a platform admin or the reseller for that tenant, open the Chatbot screen for the chosen tenant.
  3. Write a tight persona. State who the bot is, the topics it may handle, and explicit escalation rules (“hand off billing changes, account actions and upset customers”). A focused persona is the single biggest lever on quality.
  4. Pick channels conservatively. Start with one channel — webchat is a good first choice. Leave the others unticked.
  5. Set a low turn budget. Start with max_turns of 2–3 so the bot hands off early while you build confidence. Set a friendly hand-off message.
  6. Enable and save. Flip Enabled on and click Save bot config. The bot is now live for that tenant on the chosen channels.
  7. Watch a few real conversations. Observe replies and hand-offs. Tighten the persona, then widen channels and raise the turn budget only once you are satisfied.

21.5 Worked example — a bot conversation, start to finish

Let us trace one inbound web-chat message for the tenant Acme Retail, whose bot is enabled on webchat with max_turns = 3 and the persona from the screen above. The CloudCX AI key is set.

  1. Visitor opens a chat and sends: “Hi, where is my order #10492? It said delivered but I don’t have it.” The thread is created on webchat and the inbound message is stored.
  2. The platform asks the bot. Acme has an enabled config, webchat is in its channels, no human owns the thread, the turn budget (3) is untouched, and the key is set — so the bot takes the turn.
  3. CloudCX AI is consulted. The transcript (one customer line) plus Acme’s persona go to CloudCX AI, which returns: {"reply": "I’m sorry your order #10492 hasn’t arrived… Could you confirm the delivery postcode so I can check the courier status?", "handoff": false}.
  4. The bot replies. Because handoff is false, the reply is stored as an outbound message from AI Assistant, delivered to the chat widget, and broadcast live. Turn count is now 1. The thread stays open.
  5. Visitor answers with the postcode. The bot is asked again (turn count 1, budget 3), consults CloudCX AI, and replies with the courier’s tracking note. Turn count is now 2.
  6. Visitor escalates: “This is the third time. I want a refund and to speak to someone.” CloudCX AI returns {"reply": "", "handoff": true} — a refund plus an explicit request for a person.
  7. The bot hands off. The thread is routed to the web-chat queue and assigned to an available agent; the configured hand-off message (“Connecting you with a teammate…”) is posted so the customer sees a response immediately. The bot is done; the thread is now a human’s.
  8. The agent takes over in their unified inbox, clicks Analyze in AI Assist, sees negative · −0.7, a summary of the refund/escalation, and a suggested reply — and resolves it as a person, with the full bot exchange already in the thread.

Notice what the turn budget bought you: even if the customer had kept chatting without escalating, the bot would have handed off automatically after its third reply — so a conversation never spirals indefinitely in the bot. And notice the seam between bot and human is invisible to the customer: one continuous thread, no “please start again.”

Try it — tune a bot in a test tenant

In a non-production tenant, with the CloudCX AI key set:

21.6 Call transcription — turning a recording into text and insight

On the voice side, CloudCX can transcribe a call’s recording into searchable text and then run the same CloudCX AI insight model over that text for an after-call sentiment and summary. The speech-to-text step uses a pluggable provider — CloudCX Speech by default — configured under the stt credential. The AI fields are filled only if the ai key is also set; with STT alone you still get the transcript, just without sentiment and summary.

The result is stored as one call transcript per call, keyed by the call’s CloudCX Switch UUID — the same id the call record and the on-disk recording share. The row carries the full text, an optional language, the provider used, optional per-utterance segments (with speaker labels when the provider supplies them), and the AI-derived sentiment and summary.

There are two ways a transcript comes into being:

To read a stored transcript, GET /voice/calls/{uuid}/transcript returns the text and its AI fields (a 404 if the call has not been transcribed yet). Both routes validate that the UUID is a real UUID, and a transcript is tenant-scoped — a tenant user can only read or re-transcribe their own tenant’s calls.

admin.cloudcx.app
Call detail// +65 6123 0099 → Acme · 03:41
CompletedAO
Voice

Call transcript

UUID a1b2c3d4… · provider CloudCX Speech · language en

Re-transcribe
Sentiment
Positive
AI · +0.58
Duration
3:41
221s
Words
612
transcribed

AI summary

CloudCX AI

The customer called about a delayed delivery for order #10492. The agent confirmed the parcel was with the courier, arranged re-delivery for the next day, and offered a goodwill voucher. The customer was satisfied and the issue was resolved on the call.

Transcript

Speaker 0 · 0:02 “Hi, I’m calling about order ten-four-nine-two, it still hasn’t arrived.”
Speaker 1 · 0:09 “Of course — let me check the courier status for you right now…”
Speaker 0 · 0:31 “Tomorrow works, thank you so much.”
1
2
3
4
  1. Re-transcribe — re-runs STT + AI on demand for this call, overwriting the stored transcript. Requires the stt key (a 503 otherwise) and the recording on disk.
  2. Sentiment KPI — the AI-derived sentiment and score for the whole call. Present only when the ai key is set; otherwise this tile is blank but the transcript still appears.
  3. AI summary — a CloudCX AI after-call summary of the conversation, the same insight model used for text threads.
  4. Transcript — the full text, split into speaker-labelled, time-stamped utterances when the STT provider returns segment timing.
A transcribed call — speaker-segmented text plus an AI sentiment and summary derived from it.

21.6.1 Procedure — enable call transcription

  1. Obtain a speech key. Create an API key with your speech vendor (the CloudCX Speech service).
  2. Fill the STT card. In PlatformPlatform Credentials, put the key in the stt card’s API key. Leave Provider blank to use the CloudCX Speech service. An optional model id is accepted.
  3. (Recommended) Ensure the AI key is set. With the ai key present too, each transcript is enriched with a CloudCX AI sentiment and summary. Without it, you still get clean text.
  4. Confirm recordings are on. Transcription works from the call’s recording, so recording must be enabled for the calls you want transcribed. New completed calls will then transcribe automatically on hang-up.
  5. Verify. Place a short test call, hang up, wait a moment, then open the call’s transcript (or call GET /voice/calls/{uuid}/transcript). You should see the text and, if the AI key is set, a sentiment and summary.
Transcription never disrupts the call

The whole pipeline is designed to be invisible to live telephony. It runs after the call is over, never raises into the call path, and degrades to a no-op when STT is unconfigured or a recording cannot be found. Turning STT on cannot affect call quality or routing; turning it off simply stops new transcripts being produced. Stored transcripts are analytics artefacts that survive even tenant deletion, mirroring how call records keep history.

21.7 Text-to-speech — synthesised announcements

The final piece is the inverse of transcription: text-to-speech. Configured under the tts credential (the CloudCX Speech service, with an optional voice id), it turns text into spoken audio. It powers spoken announcements in voice flows and provides a POST /voice/tts endpoint that synthesises a short piece of text and returns the audio — useful for testing a voice and prompt before wiring it into a flow. Like the others, it returns a clean 503 when no tts key is set.

This makes a complete “voice AI” pairing: STT understands what callers say (and feeds CloudCX AI for insight), and TTS speaks back to them. They are configured and billed independently, so you can run one without the other.

Roll out AI one capability at a time

You do not have to enable everything at once. A common, low-risk path: (1) set the CloudCX AI key and let agents use AI Assist for a week; (2) enable the chatbot on web chat for one friendly tenant with a tight persona and low turn budget; (3) add STT so calls are transcribed and summarised; (4) finally add TTS for spoken flows. At each step the rest of the platform is unaffected, and you can stop at whichever level fits the customer.

21.8 Operating, troubleshooting and cost

A handful of facts will resolve almost every “why isn’t the AI working?” question:

Table 21.2 — common AI symptoms and what they mean
SymptomMost likely causeWhat to check
AI Assist shows “not enabled”; buttons inertNo ai key (route returns 503)Set the AI key in Platform Credentials; the panel reads /ai/health.
Bot is enabled but never repliesAI not configured, or channel/turns/ownership gateConfirm the ai key is set; confirm the thread’s channel is ticked and the turn budget is not spent.
“No messages to analyze”Empty thread (422)Analyze after at least one message exists in the thread.
Transcript request returns 503No stt keySet the STT key; this is separate from the AI key.
Transcript exists but no sentiment/summarySTT set, ai key not setAdd the AI key to enrich transcripts; text is still produced without it.
Transcribe returns 404 “no recording”Recording missing for that callConfirm recording was enabled and the file exists for the call UUID.

On cost: text intelligence is billed by CloudCX per token at the configured model’s rate — the platform default model is a mid-tier model chosen for a good quality-to-cost balance. Three design choices keep spend in check without you doing anything: insight results are cached per unchanged thread (§21.3.1), the chatbot has a per-thread turn budget that caps how many model calls one conversation can trigger, and transcripts are produced once per call and re-used. Speech is billed separately by your STT/TTS vendor, per minute of audio. If you need to control AI cost for a tenant, the chatbot’s max_turns and channel scope are your most direct levers.

One shared key, used everywhere

Remember that the ai credential is platform-wide. Removing it to stop one tenant’s bot also disables AI Assist and transcript insights for every tenant. To stop a single tenant’s bot, disable that tenant’s bot config — do not pull the shared key. Reserve key rotation/removal for genuine platform-wide changes (a compromised key, a billing switch, a model migration).

21.9 Summary

CloudCX’s AI features are a coherent set built on two providers and one rule. CloudCX AI powers all text intelligence — the AI Assist insights (sentiment, summary, topics), the suggested replies agents can insert, the after-call summary/sentiment on transcripts, and the opt-in chatbot — all from one shared ai key. A dedicated speech layer (CloudCX Speech by default) powers transcription under stt and TTS under tts. Every secret lives encrypted in the admin-only Platform Credentials vault, never in the open. And the rule that makes all of it safe to adopt: AI is additive and fail-safe — nothing is on until you set a key, the chatbot is opt-in per tenant and can never break inbound, and transcription never touches a live call. Enable what serves your customers, in the order that suits them, and leave the rest dark with zero side-effects.

CloudCX Administrator & Training Manual · Chapter 21 — AI: insights, replies, bots and transcription
CloudCXCloudCX Administrator & Training Manual
Ch. 22 · Contacts & CRM
Chapter 22

Contacts and CRM

Behind every call, chat, WhatsApp message and email is a person. CloudCX gives that person a stable identity — a Contact — so an agent always knows who they are talking to, can read the customer’s whole history at a glance, and can leave notes the next agent will need. This chapter covers the built-in contacts directory that backs the Agent Desktop’s customer panel, how a visitor is automatically resolved to a contact, the merged interaction timeline stitched from digital threads and call records, and the optional HubSpot connector that mirrors your contacts into an external CRM of record. It closes with a fully worked contact resolve and a hands-on Try it exercise.

A lightweight CRM, by design

The CloudCX contacts directory is a lightweight CRM: just enough to give agents a customer identity, profile, notes and timeline that persist across interactions. It is not a replacement for a full sales CRM. When your organisation already runs HubSpot (or, later, Salesforce / Zoho), the connector in §22.5 keeps the two in step — CloudCX stays the system of engagement, your CRM stays the system of record.

22.1 What a contact is

A contact is the stable record of one customer. It is deliberately minimal — the platform never tries to be a marketing database — but it carries everything an agent needs in the moment. Two fields do the heavy lifting for matching: the loosely-normalised phone and the email. Both are indexed, so CloudCX can find-or-create a contact from an inbound interaction in a single lookup, and a search box can match on either.

Namename
The customer’s display name. Optional — an anonymous web-chat visitor or an unknown caller has no name until you learn one, and the platform happily creates a nameless contact keyed on their number.
Phonephone
A telephone number, stored loosely normalised: a leading + is preserved and everything else is reduced to digits. So +1 415 555 0182, (415) 555-0182 and 14155550182 all collapse to the same matchable key. Indexed.
Emailemail
An email address. Matched case-insensitively ([email protected] equals [email protected]). Indexed.
Companycompany
The organisation the customer belongs to. Free text.
Titletitle
The customer’s job title or role. Free text.
Notesnotes
A free-form internal note, visible to agents and supervisors only — never to the customer. The Agent Desktop saves this field for you as you type (§22.4.3).
Customcustom
An arbitrary JSON map of extra fields, e.g. {"tier":"Enterprise","account":"AC-77120","vip":true}. Each key becomes a labelled row in the CRM record card, and a vip flag lights the VIP badge.
Tenanttenant_id
The owning tenant. Nullable by design: a web-chat visitor is anonymous until their conversation is routed to a tenant, so an early contact may not yet be bound to one (it mirrors how threads and call records behave).
Contacts are isolated per tenant

Every read, search, resolve and edit is scoped to the signed-in user’s tenant. An agent can only ever see, match or attach to a contact belonging to their own tenant; resolving an interaction can never surface another tenant’s customer. A platform administrator (who has no tenant) sees the tenant-less, anonymous contacts. This boundary is enforced by the API on every call — you cannot opt out of it.

22.1.1 Where contacts come from

Automatically, on resolve

The usual path. When an agent opens an interaction that carries a visitor identity (a phone number or email), the desktop resolves it — finding the existing contact or creating a new one. Agents never have to add contacts by hand for them to appear (§22.3).

Created directly

Anyone authenticated can create a contact through the API (POST /contacts) — useful for seeding a directory or for an integration that imports customers ahead of their first contact.

Edited as you learn

A contact grows over time. An agent adds a name, company or a note; the next interaction enriches it further. A profile that started as just a phone number becomes a rounded customer record.

Mirrored to your CRM

When the HubSpot connector is configured, contacts can be pushed outward to your external CRM — one at a time or in bulk — so your CRM stays current with the customers CloudCX is talking to (§22.5).

CloudCX · Administrator & Training ManualChapter 22 · Contacts & CRM
CloudCXCloudCX Administrator & Training Manual
Ch. 22 · Contacts & CRM

22.2 The contacts directory

The directory is the searchable list of every contact in your tenant, newest first. It backs both the customer panel on the Agent Desktop and any administrative listing. A single search box matches against the name, phone or email at once, so an agent can paste in a number, type part of a name, or drop in an email and find the same record either way.

cloudcx.app/agent
Customer
Contacts312
History
Notes
REGISTERED · SIP OK
ext 4021 · agent
Contacts// directory · tenant Acme
AO
Customer · directory

Contacts

Search by name, phone or email. Newest first.

+ New contact
NamePhoneEmailCompanyAdded
JLJordan Lee+1 415 555 0182[email protected]Northwind2m ago
MSMaría Solís+34 612 000 210[email protected]1h ago
DODaniel Okonkwo+44 7700 900221[email protected]Acme CorpYesterday
?Unknown caller+1 312 555 77883d ago
1
2
3
4
5
  1. Contacts nav item — the directory for the current tenant; the count is the number of contacts you can see.
  2. Search — one box matches name, phone or email with a case-insensitive partial match. Paste a number, a fragment of a name, or an address.
  3. New contact — create a record by hand for a customer you already know.
  4. Contact row — initials avatar, name, the normalised phone, email and company, with how recently it was added.
  5. Nameless contact — a record keyed only on a phone number (an unknown caller). Perfectly valid; an agent fills in the name when they learn it.
The contacts directory with a phone-number search. The same query would match this contact whether typed with spaces, brackets or as raw digits.

22.2.1 How search and listing behave

Table 22.1 — Listing & search parameters
ParameterEffectDefault · bounds
qPartial, case-insensitive match against name OR phone OR email. Omit it to list everyone.none (lists all)
limitMaximum number of rows returned.50 · between 1 and 500
OrderAlways newest contact first (by creation time).fixed
ScopeRestricted to your tenant’s contacts; a platform admin sees tenant-less contacts.enforced

Procedure — find a contact

  1. Open the directory from the CustomerContacts panel, or simply open the interaction — the matching contact is resolved for you automatically (§22.3).
  2. Type your query. Enter any part of the name, the phone number in any format, or the email address. The list filters as you search.
  3. Read the row. The avatar, name, normalised phone, email and company tell you who it is at a glance.
  4. Open the contact to see the full profile, the interaction history, and the notes (§22.4).
Tip · you never have to reformat a number

Because phone matching is on the digits-only form, you can paste a number exactly as the customer wrote it — with country code, spaces, dashes or brackets — and still land on the right contact. There is no “correct” format to remember.

CloudCX · Administrator & Training ManualChapter 22 · Contacts & CRM
CloudCXCloudCX Administrator & Training Manual
Ch. 22 · Contacts & CRM

22.3 Resolving a visitor to a contact

The single most important behaviour in the contacts system is resolve — a find-or-create. When an agent opens an interaction whose visitor has an identity (a phone number for a call or SMS, an email for an email thread, a number or address for chat/WhatsApp), the Agent Desktop hands that identity to the platform, which either returns the matching contact or creates a fresh one on the spot. The desktop therefore always has a stable CRM record to attach the conversation to — even for a first-time, never-seen caller.

Resolve — find an existing contact, or create one
Visitor identity
phone or email
Normalise
digits-only / lower-case
Look up
in this tenant
Contact
found · or created

22.3.1 How matching works

The identity passed in is either an email or a phone, and CloudCX decides which by a simple rule: if it contains an @ it is treated as an email, otherwise as a phone.

Resolve never blanks a profile

Resolve only ever adds. When it finds a contact it returns it untouched, except for backfilling a name that was previously missing. It never overwrites a name, company or note you have already set. Enriching the rest of the profile is a deliberate edit (§22.4), not a side-effect of an interaction arriving.

22.3.2 What the agent sees

Resolve is invisible in the normal flow: the agent claims a conversation and the customer panel simply fills in. Under the hood the desktop resolves the visitor, renders the profile, loads the history, and binds the notes box — all before the agent types a word. If anything fails, the panel degrades gracefully and the rest of the desktop is unaffected; the conversation is never blocked on the CRM.

Table 22.2 — Resolve outcomes
Visitor identityDirectory stateResult
Known email or phoneA matching contact existsThat contact is returned and shown; a missing name is backfilled if one was supplied.
New email or phoneNo matchA new contact is created (with name if known) and shown.
Anonymous visitorNo identity to matchNo resolve happens; the panel shows the demo / placeholder context and no contact is created.
Tip · resolving is idempotent

Re-opening the same interaction, or a different conversation from the same customer, resolves to the same contact — the platform caches the resolution per interaction and matching is deterministic. You will not end up with duplicate records for one customer just because they reached out twice.

CloudCX · Administrator & Training ManualChapter 22 · Contacts & CRM
CloudCXCloudCX Administrator & Training Manual
Ch. 22 · Contacts & CRM

22.4 The customer panel on the Agent Desktop

Everything in this chapter comes together in the right-hand Customer column of the Agent Desktop (Chapter 17). When a contact is resolved, the panel shows four stacked cards: the profile, the CRM record, Recent interactions, and private Internal notes. The same panel travels with the agent across every channel, so the customer’s context never disappears when they switch from chat to a call.

cloudcx.app/agent
Jordan Lee// voice · +1 415 ••• 0182
REC
// stage
The conversation is on the Stage. The customer’s record — resolved automatically — fills the panel on the right.
JL
Jordan Lee
+1 415 ••• 0182 · jordan@…
★ VIP · Priority
CRM recordCR-7F3A91C2
CompanyNorthwindTitleOps LeadTierEnterpriseAccountAC-77120
Recent interactions4 records
Inbound callanswered
WhatsAppclosed
Outbound callmissed
Invoice queryopen
Internal notesprivate
Prefers email follow-up. Knows the product well — skip the basics.
1
2
3
4
  1. Profile card — initials avatar (brand gradient), the name, a phone · email line, and a VIP badge shown when the contact’s custom map carries a vip flag.
  2. CRM record — Company, Title, Phone and Email, followed by every key from the custom map (here Tier and Account). The CR-… id is a short, stable handle for the contact.
  3. Recent interactions — the merged timeline (§22.4.1): a coloured channel dot, the interaction title, and an outcome pill, newest first, with a total count.
  4. Internal notes — a private scratch-pad saved automatically as you type (§22.4.3). Visible to agents and supervisors only.
The Customer panel for a resolved contact: profile, CRM record (built-in fields plus custom keys), recent interactions and private notes.

22.4.1 The interaction timeline

The Recent interactions card is a single merged timeline stitched from two sources and sorted newest-first:

The two streams are interleaved by time into one list, so the agent reads the customer’s whole relationship — voice and digital together — in one place, capped at the most recent items.

CloudCX · Administrator & Training ManualChapter 22 · Contacts & CRM
CloudCXCloudCX Administrator & Training Manual
Ch. 22 · Contacts & CRM

22.4.2 Reading the timeline

Each row in the timeline is colour-coded by channel and tagged with an outcome so the agent can scan a customer’s history in seconds. The dot follows the channel palette used everywhere in the platform; the outcome pill maps the underlying status to a clear colour.

Table 22.3 — Timeline row anatomy
ElementVoice (call)Digital (thread)
Kindcallomni
Channel dotVoice (magenta)Chat blue · WhatsApp green · SMS amber · Email violet · Social pink
TitleInbound call or Outbound callThe thread subject, or a channel label (e.g. Web chat, WhatsApp)
Outcome pillanswered or missedclosed/resolved, open, escalated
AgeRelative time of the interaction (e.g. 2m, 1h, Yesterday), newest first.
Why a call can appear inbound and match the contact

The timeline matches a call when the contact’s number is either the caller or the destination. That is deliberate: a customer who phones in (they are the caller) and a customer you dialled out to (they are the destination) are both their calls. Matching both directions means a contact’s history is complete regardless of who placed each call.

22.4.3 Editing a contact and its notes

A contact is enriched by editing. An agent who learns the customer’s name, company or role updates the profile; the Internal notes box captures context for whoever handles the customer next. Edits are partial — only the fields you change are written, so updating a note never disturbs the name, and clearing a phone removes just that field.

Procedure — enrich a contact during a call

  1. Open the interaction. The contact resolves and the panel fills (§22.3). For a never-seen caller it may be a bare number with no name.
  2. Add what you learn. Set the name, company and title as the conversation reveals them. Each change is saved as a partial update — nothing else on the record is touched.
  3. Write a note. Type into the Internal notes box. The desktop saves it automatically a moment after you stop typing (and again when you click away), confirming with a small Notes saved toast.
  4. Add custom fields where your tenant uses them — a tier, an account number, a VIP flag — and they appear as labelled rows in the CRM record card on the next render.
Warning · notes are internal, not invisible

Internal notes are never shown to the customer, but they are visible to every agent and supervisor who opens the contact, and they persist across interactions. Write them professionally: useful operational context (“prefers email; account on Enterprise tier”), never personal opinion or anything you would not want the customer—or an auditor—to read.

Tip · the wrap-up note lands here too

When you record a disposition at the end of a chat (Chapter 17), CloudCX can append the outcome, tags and your wrap-up note to this same contact’s notes field. Over time the notes become a running log of how the customer’s issues were handled — which is exactly what the next agent wants to read first.

CloudCX · Administrator & Training ManualChapter 22 · Contacts & CRM
CloudCXCloudCX Administrator & Training Manual
Ch. 22 · Contacts & CRM

22.5 The HubSpot connector

CloudCX can mirror its built-in contacts — and log interactions against them — into an external CRM of record. HubSpot ships today (Salesforce, Zoho and others can be added later behind the same interface). The connector is an administrator feature: it is configured once with a HubSpot token, after which contacts can be pushed out one at a time or in bulk. Until it is configured, the whole feature is a graceful no-op — it ships dark and an admin turns it on at runtime.

CloudCX contacts mirrored into HubSpot
CloudCX contactname · phone · email · company · title · notes · custombuilt-in
CRM connectormaps neutral fields → HubSpot properties; de-dupes on email, else phoneapp.crm
HubSpot CRM v3contact record (firstname/lastname/email/phone/company/jobtitle) + Note activitiessystem of record

22.5.1 What the connector does

Upsert a contact

Create-or-update a HubSpot contact, keyed on email (or, failing that, phone). The connector searches HubSpot for an existing record; if found it updates it, otherwise it creates a new one. Your name is split into HubSpot’s firstname/lastname, and company/title map to company/jobtitle.

Log an interaction

Ensure the contact exists, then attach a HubSpot Note activity carrying a short interaction summary (and a channel/direction tag), associated to that contact — so a call or chat in CloudCX leaves a trail in your CRM.

Table 22.4 — Field mapping, CloudCX → HubSpot
CloudCX fieldHubSpot propertyNotes
namefirstname + lastnameSplit on the first space: first token is the first name, the rest the last name.
emailemailPrimary de-duplication key.
phonephoneFallback de-duplication key when there is no email.
companycompany
titlejobtitle
customnot auto-writtenCustom keys are not pushed (they would need matching custom properties in your portal).
An upsert never blanks a field in HubSpot

The connector only ever sends the values it actually has — empty fields are omitted from the request. So syncing a CloudCX contact that has no company will not wipe a company an operator set directly in HubSpot. The sync is additive, and it is safe to run repeatedly.

22.5.2 Configuring the connector

The connector reads CloudCX’s encrypted platform credentials under the crm provider. An administrator stores two fields — the provider (hubspot) and the token (a HubSpot private-app access token) — in the platform integration credentials, the same screen used for channel providers (Chapter 12). The token is write-only and is never returned by any status call.

admin.cloudcx.app
Platform
Users
Integrations
Channels
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Integrations// platform credentials
PRODAO
Platform · integrations

External CRM

Mirror contacts into your CRM of record.

CRM credentials

×
ProviderHubSpot
API token *pat-na1-••••••••••••••••••••HubSpot private-app access token. Stored encrypted; write-only.
Not configuredSave a token to enable sync.
CancelSave credentials
1
2
3
4
  1. Integrations — the platform credentials screen (Chapter 12), where channel and CRM providers are configured.
  2. Provider — the CRM connector to use. HubSpot today; the field exists so further providers can be selected later.
  3. API token — the required HubSpot private-app token. Stored encrypted and write-only; no status call ever returns it.
  4. Status & save — until a token is saved the connector reads Not configured; once saved, sync is enabled.
Configuring the CRM connector under Platform › Integrations: choose the provider and paste the private-app token.

Procedure — connect HubSpot

  1. Create a private app in HubSpot with the CRM scopes to read and write contacts and create notes (engagements). Copy its access token.
  2. Open PlatformIntegrationsExternal CRM in the admin console.
  3. Set the provider to HubSpot and paste the token into the API-token field.
  4. Save credentials. The connector status flips to Configured · HubSpot. You can now sync contacts (§22.5.3).
CloudCX · Administrator & Training ManualChapter 22 · Contacts & CRM
CloudCXCloudCX Administrator & Training Manual
Ch. 22 · Contacts & CRM

22.5.3 Syncing contacts

With the connector configured, an administrator can push contacts to HubSpot two ways: a single sync of one contact, or a bulk sync of the whole tenant’s directory. Both report exactly what happened — created, updated or failed — and neither ever throws: a problem with one contact is counted, not allowed to abort the run.

admin.cloudcx.app
External CRM// HubSpot · connected
Configured · HubSpotAO
Considered
312
this run
Created
47
new in HubSpot
Updated
261
matched on email
Failed
4
see log

Sync this tenant’s contacts

Sync all →
ContactActionHubSpot idResult
JLJordan Leeupdate301-552-118synced
MSMaría Solíscreate301-552-940synced
?Unknown callerno identifier
A completed bulk sync. The KPI tiles summarise the run; the table shows each contact’s action and result, including a contact that could not be keyed.
Table 22.5 — Sync operations & results
OperationScopeReports
Single syncOne contact by id (admin).action = create / update / noop, and the external crm_id; or an error string.
Bulk syncYour tenant’s contacts, newest first, up to a hard cap of 500 per run, pushed in small concurrent batches.Totals: considered, synced, created, updated, failed, and truncated when more contacts exist than the cap.
StatusWhether a CRM is configured and which provider.{configured, provider} — never the token.

Procedure — bulk-sync your directory

  1. Confirm the connector is configured (§22.5.2). The status must read Configured; otherwise sync returns a clear “CRM is not configured” error rather than failing obscurely.
  2. Run Sync all. CloudCX selects your tenant’s contacts (up to 500, newest first) and pushes them to HubSpot in small batches to respect its rate limits.
  3. Read the summary. Note created vs updated, and any failed count. A contact with neither email nor phone cannot be keyed in HubSpot and is reported as failed — that is expected for a bare, nameless record.
  4. Re-run if truncated. If truncated is true, more than 500 contacts matched; run again later to continue. Because upserts are idempotent, re-running never duplicates.
Warning · sync follows the tenant boundary

A bulk sync only ever pushes the calling admin’s tenant’s contacts. A platform administrator (with no tenant) syncs the tenant-less, anonymous contacts — not every tenant’s directory. If you intend to mirror a specific customer’s contacts to HubSpot, run the sync signed in as an administrator of that tenant.

The connector fails softly

If HubSpot is slow or unreachable, the connector times out quickly and reports the failure for the affected contacts rather than wedging the request or the batch. If the crm credentials are removed, every sync simply becomes a no-op again. Nothing in the contacts directory depends on HubSpot being reachable.

CloudCX · Administrator & Training ManualChapter 22 · Contacts & CRM
CloudCXCloudCX Administrator & Training Manual
Ch. 22 · Contacts & CRM

22.6 Worked example — a contact resolve, end to end

This follows one caller from the moment the phone rings to a record mirrored in HubSpot — the full arc the contacts system runs, automatically, many times an hour.

  1. A call arrives. A customer dials in from (415) 555-0182. The ACD routes it to agent Ada, whose desktop pops the screen-pop with the caller’s number.
  2. Resolve runs. The desktop sends the number to be resolved. It is not an email (no @), so it is treated as a phone: reduced to the digits 4155550182 and looked up in Ada’s tenant.
  3. A match is found. A contact with phone +14155550182 already exists — Jordan Lee, from a previous chat. The same digits match despite the different formatting, so the existing record is returned. No duplicate is created.
  4. The panel fills. Jordan’s profile renders: name, the phone · email line, and a VIP badge (his custom map has vip: true). The CRM card shows Company Northwind, Title Ops Lead, plus the custom Tier and Account rows.
  5. History loads. The timeline stitches Jordan’s prior WhatsApp thread (closed) and an earlier inbound call (answered) into one newest-first list — so Ada opens with “Hi Jordan, good to hear from you again.”
  6. Ada takes a note. Mid-call she learns Jordan now leads a second team; she types it into Internal notes. A second after she stops, it saves automatically — only the notes field is written; nothing else on the record changes.
  7. The admin mirrors it. Overnight, the tenant administrator runs Sync all. Jordan already exists in HubSpot (matched on his email), so he is updated, not created; the run reports 1 updated for him and an external HubSpot id.
# 1) resolve the inbound number (find-or-create, tenant-scoped) POST /api/v1/contacts/resolve { "contact": "(415) 555-0182", "name": "Jordan Lee" } → 200 { "id": "7f3a91c2-…", "name": "Jordan Lee", "phone": "+14155550182", "company": "Northwind", "custom": { "tier": "Enterprise", "vip": true } } # 2) read the merged timeline (omni threads + calls, newest first) GET /api/v1/contacts/7f3a91c2-…/history → 200 [ { "kind": "call", "title": "Inbound call", "status": "answered" }, { "kind": "omni", "channel": "whatsapp", "status": "closed" } ] # 3) save a note (partial update — only `notes` is written) PATCH /api/v1/contacts/7f3a91c2-… { "notes": "Now leads a second team — loop in on roadmap calls." } # 4) admin mirrors the directory into HubSpot (idempotent) POST /api/v1/crm/sync-all → 200 { "total": 312, "created": 47, "updated": 261, "failed": 4, "truncated": false }
Try it · build, resolve and mirror a contact

Goal: see find-or-create, the timeline and a HubSpot sync work end to end, using the same tools agents and admins use.

Check: one contact — not two — for the reused number; the custom tier visible on the card; both interactions in the timeline; and a HubSpot id returned by the sync with the action flipping createupdate on the second run.

22.7 Glossary

Contactnoun
The built-in CRM record for one customer — name, phone, email, company, title, notes and a free-form custom map — scoped to a tenant.
Resolvefind-or-create
Turning an interaction’s visitor identity (a phone or email) into a contact: returning the existing match or creating a new record. Idempotent and tenant-scoped.
Normalisationphone
Reducing a phone to a leading + (if present) plus digits only, so every way of writing a number matches the same contact.
Timelinehistory
The merged, newest-first list of a contact’s interactions, stitched from digital threads (matched on visitor identity) and calls (matched on caller or destination).
ConnectorCRM
The provider integration that mirrors contacts into an external CRM of record (HubSpot today). Configured per platform under the crm credentials; a no-op until a token is stored.
Upsertcreate-or-update
A single operation that creates the contact in the external CRM if it is missing or updates it if it exists, de-duplicating on email then phone. Additive — it never blanks a field.
Where to go next

The customer panel lives inside the Agent Desktop — Chapter 17 covers handling the interactions whose context this chapter supplies. The CRM connector’s credentials sit alongside the channel providers configured in Chapter 12. Outbound campaign dialling, which also reads and dispositions contacts, is covered in Chapter 18.

CloudCX · Administrator & Training ManualChapter 22 · Contacts & CRM
CloudCXCloudCX Administrator & Training Manual
Ch. 23 · Billing I
Chapter 23

Billing I — model, rate cards, invoicing and settlement

Every minute of voice, every SMS segment and every WhatsApp message that flows through CloudCX can carry a price. This chapter is the operator’s guide to how that money is modelled and moved: the immutable ledger and the per-party accounts that sit on it, the wholesale and retail rate cards that turn raw usage into priced charges, the period-close that groups those charges into invoices, and the two ways an invoice gets paid — online through a bring-your-own Stripe account, or manually by recording a wire or a credit. It closes with a full worked billing cycle and a Try-it exercise.

Where this chapter sits, and what it does not cover

This is Billing I — the core money path that turns usage into a paid invoice. Billing II (the next chapter) layers on prepaid & credit control (top-ups, credit limits, low-balance alerts), tax & FX configuration, dunning (overdue collections, reminders, suspend/unsuspend) and the finance reports (revenue, margin per reseller, receivables aging, reconciliation). You will see those panels in the console here too — note them, then turn the page for the detail. Everything in this chapter lives under admin.cloudcx.appBilling, with the matching wholesale/retail rate-card surfaces under Billing and the partner console.

Idle-safe by design — nothing bills until you say so

CloudCX ships with the billing engine present but dormant. With no rate cards, the rater runs to completion and charges exactly zero — unrated usage is simply left for later, and the miss is logged once. With no Stripe configured, online settlement is unavailable and manual settlement still works. You therefore turn billing on deliberately, card by card. An empty billing database has zero effect on a running platform.

23.1 The money model — parties, accounts and the ledger

Three kinds of party take part in money flows, and they map exactly onto the platform hierarchy you already know from Chapter 2: the platform (CloudCX itself), the resellers, and the tenants. Money moves across three relationships between them:

The three billing relationships
PlatformCloudCX
Resellerwholesale
Tenantretail

A reseller’s tenant generates two charges for the same usage: a retail charge (tenant owes the reseller, priced off the reseller’s card) and a wholesale charge (reseller owes the platform, priced off the platform card). A direct tenant — one with no reseller — has only the platform relationship: its “retail” is the platform card, and there is no second leg.

23.1.1 Billing accounts

Each party has exactly one billing account — its whole money position in one place. An account is keyed by (party_type, party_id): platform with a NULL id (there is one platform account), reseller with the reseller id, or tenant with the tenant id. Accounts are created on first use: the first charge, payment or top-up that touches a party auto-creates its account, so you never have to pre-provision them.

Party / type party_type
Which party the account belongs to: platform / reseller / tenant. The Billing → Billing accounts table shows every account, platform-first.
Currency currency
The account’s settlement currency (default USD). All of an account’s invoices are issued in this currency; a charge in a different currency is converted via FX at invoice time (Billing II).
Mode billing_mode
postpaid (invoice then collect — the default, and the subject of this chapter) or prepaid (collect then spend — Billing II).
Balance balance
The maintained running total. Positive means the party owes us; negative means they are in credit. This is a fast-read cache of the journal (below) and is the figure shown in the accounts table.
Status status
active / suspended / closed. Suspension (a dunning action) is covered in Billing II.

23.1.2 The journal — one immutable source of truth

Behind every balance is an append-only journal: one row per money event, never updated and never deleted. Each row carries a signed amount in “what the party owes us” terms and a balance-after snapshot, so the journal alone can reproduce a running statement. The maintained balance on the account is just a cache of this journal, moved in the very same database transaction as the row that justifies it, under a row lock so two concurrent posts can never race it.

Table 23.1 — The journal sign convention (in “owes us” terms)
Event (txn_type)SignEffect on balancePosted by
chargepositiveIncreases what they oweThe rater (rating a CDR / message / seat)
paymentnegativeDecreases what they oweA confirmed Stripe payment, or a recorded wire
creditnegativeDecreases what they oweA goodwill credit you post
adjustmentnegativeDecreases what they oweA correcting adjustment you post
refundnegativeDecreases what they oweA refund you post
An invoice is a statement, not a money event

This is the single most important idea in CloudCX billing. The balance moves when usage is rated — the moment a charge is posted. An invoice later groups charges that are already on the ledger into a presentable document; posting an invoice does not move the balance. (Doing so would double-count every charge.) Only a payment or a credit writes the negative row that pays the balance down. Keep this in mind whenever a balance and an invoice total seem to disagree — see § 23.6.

23.1.3 Charges — the priced line items

A charge is one billable line: a rated call, a rated message batch, a monthly DID rental, a seat, a one-off. Two account references make the multi-level model work: account_id is the payer (who owes) and counterparty_account_id is who is owed. A reseller-tenant’s voice charge therefore has the tenant account as payer and the reseller account as counterparty; the matching wholesale charge has the reseller as payer and the platform as counterparty. Every charge also carries a unique external_id (the CDR’s call_uuid, msg:<id> for a message, and so on) which is the idempotency key for usage — re-ingesting the same CDR never charges twice. A charge moves pendinginvoiced (when a period close pulls it in) and is otherwise immutable.

23.2 The Billing workspace

PlatformBilling

Open Billing from the platform sidebar. The workspace is a stack of panels that follow the money from the bottom up: the accounts and their balances, the platform’s own Stripe configuration, the wholesale rate cards, and the invoices CloudCX issues. (Below those sit the prepaid, tax/FX, dunning and reports panels covered in Billing II.) The data-source line under the heading tells you whether you are looking at the live API or fixture data — read it before you act, exactly as on every other platform view.

admin.cloudcx.app
Operate
Dashboard
Reports
Revenue
Billing$
Tenancy
Resellers12
Tenants86
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Billing// 03 — Platform billing · live
PRODAO
Revenue

Billing accounts

Every account & balance — positive means the party owes us.

Refresh
PartyTypeCurrencyModeStatusBalance
BYCloudCX (Platform) platformUSDpostpaid active−$1,204.00
ACAcme Comms resellerUSDpostpaid active$842.16
NWNorthwind Retail tenantUSDpostpaid active$318.40
HBHarbour Clinic tenantUSDprepaid active−$50.00
1
2
3
4
  1. Billing sits under the Revenue group in the dark sidebar — the active section here.
  2. The data-source line (// 03 — Platform billing · live) confirms you are on the live API, not fixtures.
  3. One row per billing account; party_type is colour-pilled (platform / reseller / tenant).
  4. Balance, positive = owes us. A reseller carries its tenants’ wholesale; a credit balance shows negative (e.g. a prepaid account in credit).
The Billing workspace, open on the Billing-accounts panel.
Reading the balances in the picture above

The platform row shows −$1,204.00 — that is the platform account’s own position, which is in credit here from refunds/credits posted to it; it is not the platform’s revenue (that lives in the reports). Acme Comms (a reseller) owes $842.16 in accumulated wholesale. Harbour Clinic is a prepaid tenant sitting on −$50.00 — i.e. $50 of available credit. Always read the sign against the “owes us” convention from Table 23.1.

23.2.1 The statement — an account’s full history

Click any account row to expand its statement: the journal rows, oldest first, each with its running balance-after. This is the authoritative history — a charge here, a payment there, a credit — and it is what you reconcile against. The statement is read-only; you change a balance only by posting a charge (via usage), a payment or a credit, never by editing the journal.

  1. Open Billing and let the Billing accounts panel load.
  2. Click the account row you want to inspect (for example a reseller you are about to invoice).
  3. Read the statement that expands below the table: each row is a txn_type with a signed amount and the balance after it. The closing balance equals the figure in the table.
  4. Cross-check the closing balance against the panel — they are always equal, because the panel value is the cache of this journal.

23.3 Rate cards — pricing usage

A rate card is a named, scoped set of per-destination rates. Its scope decides what it prices:

Platform card = WHOLESALE

Owner platform (NULL id). The rates CloudCX charges resellers and direct tenants. Managed by the platform admin under Billing → Wholesale rate cards and the /admin/rate-cards API.

Reseller card = RETAIL

Owner reseller (the reseller’s id). The rates a reseller charges its own tenants. Managed by the reseller in the partner console and the /reseller/rate-cards API — hard-scoped to that reseller.

Each card holds entries, one per channel + destination_prefix. The rater matches an event by longest prefix — so 65 (Singapore) beats 6 beats the catch-all '' (empty prefix, matches anything). Messaging entries have no dialed destination, so they use the catch-all. The scope (owner_type, owner_id) is always forced from who you are: an admin can only ever create platform cards, a reseller only its own — it is never read from the request body, and a cross-scope read returns 404 (it never even leaks that another owner’s card exists).

Table 23.2 — A rate-card entry, field by field
FieldMeaningExample
channelWhat it prices: voice, sms or whatsapp.voice
destination_prefixLongest-prefix match key. '' (empty) is the catch-all and matches anything at lowest priority.65
ratePer-unit price (per billed minute for voice; per segment for SMS; per message for WhatsApp). Six decimals — sub-cent rates are exact.0.014000
setup_feeA one-off fee added per call (e.g. a connection charge). Applied even when an allowance zeroes the per-minute portion.0.010000
increment_secondsVoice billing increment: 60 = per-minute, 1 = per-second. Ignored for messaging.60
included_unitsAllowance subtracted before charging (minutes / segments). Floors at zero.0
How a charge is actually computed

Voice: billed minutes = ceil(billsec / increment_seconds), less included_units (floored at 0), times rate, plus setup_fee. Only outbound, normally-cleared calls bill; inbound and unanswered/failed calls are free. SMS: the segment count uses the industry GSM-7 rule (160 chars in one segment, 153 per part when concatenated) with a UCS-2 fallback (70 / 67) for non-GSM bodies. WhatsApp: one billable unit per message. The rate card is data — you never write code to price usage.

23.3.1 Creating a wholesale rate card

The Wholesale rate cards panel lists the platform cards; each expands to show and edit its entries. You create the card header first (name + currency), then add entries to it. Use active to park a card without deleting it — the rater only uses active cards, and it always picks the newest active card for a scope, so re-pricing is a matter of cloning a card with new rates and activating it.

admin.cloudcx.app
Revenue
Billing$
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Billing// wholesale rate cards
PRODAO
Revenue

Wholesale rate cards

Rates CloudCX charges resellers & direct tenants.

+ New rate card

Standard Wholesale 2026   active

USD
ChannelPrefixRateSetupIncrIncl.
voice650.0080000.000000600
voice10.0110000.000000600
voice catch-all0.0300000.000000600
sms catch-all0.0040000.0000000
+ Add entryEdit header
1
2
3
4
5
  1. New rate card — creates the card header (name + currency); the scope is forced to platform.
  2. The card header shows its name, active pill and currency. The rater uses the newest active card.
  3. A voice entry for prefix 65 (Singapore) — longest-prefix match wins over 1 and the catch-all.
  4. The catch-all voice entry (empty prefix) — the floor rate for any destination not matched above.
  5. An SMS entry on the catch-all: messaging has no dialed prefix, so the catch-all applies.
A wholesale rate card with voice prefixes and a messaging entry.
  1. Open Billing and scroll to the Wholesale rate cards panel.
  2. Click “New rate card”. Give it a clear name (e.g. Standard Wholesale 2026) and the settlement currency (default USD). Leave active on. Save — the empty card appears in the list.
  3. Add a catch-all voice entry first. Click Add entry on the card: channel voice, prefix blank (the catch-all), rate your floor price, increment_seconds 60. This guarantees some voice rate matches every call.
  4. Add the specific prefixes. Add a voice entry for each destination you price keenly — e.g. prefix 65 at a lower rate. The longer prefix automatically wins for matching numbers.
  5. Add messaging entries. Add a sms and/or whatsapp entry on the catch-all prefix with the per-segment / per-message rate.
  6. Verify the card is active. The rater only prices off active cards. Done — the next rating pass will use it.
Always ship a catch-all

If no entry (not even a catch-all) matches an event, the rater leaves the usage unrated and logs the miss once — nothing is charged, and the call/message stays free until a matching card exists. That is the idle-safe guarantee, but it also means a missing catch-all silently makes a whole class of traffic free. Add the catch-all entry first, then refine with specific prefixes.

Re-pricing is forward-only — never edit history

To change a rate, you can edit the live entry (it affects only future rating passes) or, more safely, clone the card with new rates and activate the clone. Either way, charges already posted are immutable — a charge does not reference the card, so changing or even deleting a card never rewrites a balance. Editing rates does not retro-bill yesterday’s traffic.

23.4 Generating invoices — closing a period

Charges accumulate on the ledger continuously as usage is rated. Invoicing is the periodic step that closes a window and groups each account’s pending charges in that window into one invoice per account. Remember the model: this does not touch the balance — it produces a document. As it runs it:

Who issues the invoice depends on where you run the close. As the platform admin you issue the platform’s invoices — to resellers and direct tenants. A reseller, in their own console, issues invoices to their tenants; their close is hard-scoped to their own accounts, so a reseller can never invoice outside its brand. Invoice numbers are sequential per issuer per calendar year: INV-PLATFORM-2026-000123 for a platform invoice, INV-<reseller>-2026-000045 for a reseller one.

admin.cloudcx.app
Revenue
Billing$
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Billing// invoices
PRODAO
Revenue

Invoices

Invoices CloudCX issues to resellers & direct tenants.

Generate invoices+ Credit / adjustment
InvoiceAccountPeriodStatusTotalPaid
INV-PLATFORM-2026-000123Acme CommsresellerMay 2026 open$842.16$0.00
INV-PLATFORM-2026-000122Globex DirecttenantMay 2026 partial$210.00$100.00
INV-PLATFORM-2026-000121Initech DirecttenantApr 2026 paid$96.40$96.40

INV-PLATFORM-2026-000123 · Acme Comms

open · due 14 Jun
Pay onlineRecord paymentDownload PDF
1
2
3
4
  1. Generate invoices — opens the period-close dialog (Figure follows). “Credit / adjustment” posts a negative ledger row.
  2. The invoice number (INV-PLATFORM-2026-…) over the billed account; sequential per issuer per year.
  3. Status pill: open (issued, unpaid), partial (some paid), paid, overdue or void.
  4. Selecting a row reveals its actions: Pay online, Record payment and Download PDF.
The Invoices panel: the issued list and the selected-invoice actions.

23.4.1 The Generate-invoices dialog

Clicking Generate invoices opens the period-close dialog. You supply the period (a start and an inclusive end date), an optional set of accounts to restrict the close to, and the due-days offset that sets each invoice’s due date as period_end + due_days (default 14). Leave the account list empty to close every account in scope that has pending charges in the window.

Generate invoices

×
Period start2026-05-01
Period end (inclusive)2026-05-31
Accounts (blank = all in scope)All accounts with pending charges
Due in (days after period end)14Due date = period end + due days → 14 Jun 2026.
CancelClose period & issue
  1. Period start / end bound the close on each charge’s creation time; the end date is treated inclusively (a charge created any time on 31 May is in the May close).
  2. Accounts — leave blank to invoice every in-scope account with pending charges, or pick specific accounts to close just those.
  3. Due-days sets the invoice due date relative to the period end (default 14).
Figure 23.5 — The Generate-invoices (period-close) dialog.
  1. Open Billing → Invoices and click Generate invoices.
  2. Set the period. Enter the start and the inclusive end date (e.g. 2026-05-01 to 2026-05-31 for the May close).
  3. Scope the run (optional). Leave Accounts blank to close every account in scope with pending May charges, or select specific accounts to close just those.
  4. Set due-days. Accept the default 14, or enter your terms; the due date becomes period_end + due_days.
  5. Close the period. Confirm. CloudCX creates one open invoice per account that had pending charges, numbers them contiguously, renders each document, and flips the pulled-in charges to invoiced.
  6. Verify. The new invoices appear at the top of the list. Re-running the same close is safe — already-invoiced charges are not grouped again, so no duplicates are created.
Closing a period twice does no harm

Idempotency is built in. Because each pulled-in charge is flipped to invoiced, a re-run of the same period finds no pending charges for the already-closed accounts and creates nothing for them. A close over a scope with no pending charges creates no invoices at all (an empty result). You can safely re-run a close if it was interrupted.

23.5 Settling an invoice

An issued invoice is collected one of two ways. Both ultimately write the same kind of ledger row — a negative payment — but they get there differently.

Online — Stripe (bring-your-own)

“Pay online” creates a Stripe Checkout Session for the outstanding amount and returns a hosted payment URL. The ledger is credited only when Stripe confirms the payment via the webhook — never on click.

Manual — wire / credit

“Record payment” logs a received wire against the invoice immediately. A credit / adjustment posts a goodwill or correcting negative row. Both work with no Stripe at all.

23.5.1 Online settlement and the bring-your-own Stripe model

Stripe in CloudCX is per-entity / bring-your-own, mirroring the channel-credential pattern. An invoice collects into its issuer’s Stripe: a platform-issued invoice into the platform’s Stripe, a reseller-issued invoice into that reseller’s Stripe (falling back to the platform’s when the reseller has not brought their own). So a reseller’s tenants pay the reseller, and the platform’s customers pay the platform — into separate Stripe accounts.

You set the platform’s Stripe under Billing → Platform payment configuration. Secret material is write-only: the secret key and webhook signing secret are encrypted at rest (Fernet) and never shown again — the form shows only whether each is set. Leave a secret field blank to keep its current value.

admin.cloudcx.app
Revenue
Billing$
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Billing// platform payment configuration
PRODAO
Revenue

Platform payment configuration

CloudCX’s own Stripe — collects from resellers & direct tenants.

Stripe publishable key (not secret)pk_live_51K…aQ2
Stripe secret key — set ✓ (blank = unchanged)sk_live_…
Webhook signing secret — set ✓ (blank = unchanged)whsec_…
Online settlement enabled — offer Stripe checkout on invoices
Online (Stripe) Manual (wire)
Save payment configReload
1
2
3
4
5
  1. Publishable key — not secret; used client-side and shown in clear.
  2. Secret key and webhook signing secret — write-only; the label shows “set” but never the value. Blank keeps the stored secret.
  3. Online settlement enabled — the master toggle for offering Stripe checkout on invoices.
  4. The webhook secret protects the public webhook endpoint; without it, the webhook fails closed (see § 23.5.2).
  5. Settlement modes offered — advertise Online (Stripe) and/or Manual.
The platform Stripe configuration (secrets are write-only).
  1. Open Billing → Platform payment configuration.
  2. Paste the publishable key (pk_live_…) and the secret key (sk_live_…). The secret is encrypted on save and never displayed again.
  3. Add the webhook signing secret (whsec_…) from your Stripe dashboard’s webhook endpoint. This is what proves an incoming webhook really came from Stripe.
  4. Enable online settlement and tick the settlement modes you offer.
  5. Save. “Pay online” now appears on platform-issued invoices. (If your server has no credential-encryption key configured, the save is refused with a clear error rather than storing plaintext.)

With Stripe configured, collecting online is one click:

  1. Select the invoice in the Invoices panel and click Pay online.
  2. CloudCX creates a Stripe Checkout Session for the outstanding amount (total − amount_paid) in the invoice currency, tags it with the invoice / account ids, and returns the hosted payment URL.
  3. Send the payer to the URL (or open it). They pay on Stripe’s hosted page.
  4. The ledger credits itself when Stripe confirms — asynchronously, via the webhook. The invoice flips to partial or paid on its own; you do not record anything by hand.
Clicking “Pay online” never credits the ledger

“Pay online” only initiates collection — it creates the checkout and returns a URL. Money is recorded only when Stripe confirms the payment through the webhook. This is deliberate: it keeps the ledger the single source of truth and means an unpaid (or abandoned) checkout never credits an account. If a payment doesn’t land, the invoice simply stays open — re-issue the link.

23.5.2 The Stripe webhook — where online money is actually recorded

Each billing entity has its own webhook endpoint, identified in the path by owner: POST /webhooks/stripe/<owner_type>/<owner_id> (use platform for the platform’s null id). When Stripe calls it on a completed payment, CloudCX:

  1. Resolves that entity’s Stripe config and its webhook secret.
  2. Verifies the Stripe-Signature HMAC over the raw body — fail-closed: a missing secret, a bad signature, or a stale timestamp (older than 5 minutes) is rejected with a 400 and nothing is processed.
  3. Records the payment exactly once on a verified success event via the accounting service, keyed on the Stripe event id — so a Stripe retry of the same event credits only once, and the amount rolls onto the invoice (and thus its amount_paid / status).
  4. Acknowledges 200 on any verified event so Stripe stops retrying — even one it doesn’t act on.
Point Stripe’s webhook endpoint at the platform’s URL
# In your Stripe dashboard, add a webhook endpoint targeting:
https://cloudcx.app/webhooks/stripe/platform/-
# Subscribe to these events:
  checkout.session.completed
  payment_intent.succeeded
  invoice.payment_succeeded
# Then paste the endpoint's signing secret (whsec_…) into
# Billing → Platform payment configuration → Webhook signing secret.
No webhook secret = no online payments will ever post

Because verification is fail-closed, an endpoint with no signing secret stored rejects every webhook with a 400 — so checkouts complete on Stripe’s side but never credit your ledger. If “Pay online” works yet invoices stay open after payment, the webhook secret is the first thing to check.

23.5.3 Manual settlement — recording a wire or a credit

Manual settlement always works, with or without Stripe. Record payment logs a received wire (or other off-line payment) against an invoice; it credits the ledger and rolls the amount onto the invoice immediately. Credit / adjustment posts a goodwill credit, a correcting adjustment, or a refund against an account or invoice — a negative ledger row that reduces what the party owes.

Record payment · INV-PLATFORM-2026-000123

×
Amount (invoice currency)842.16Outstanding: $842.16 USD
MethodWire transfer
ReferenceSWIFT / wire ref
Notes (optional)
CancelRecord payment
  1. Amount — the non-negative amount received, in the invoice currency. Pre-filled from the outstanding balance; enter a smaller amount for a part payment.
  2. Methodwire (a received bank transfer) or manual_credit (a keyed credit/opening deposit).
  3. Reference — the wire reference / cheque number. It also forms the idempotency key, so re-submitting the same reference against the same invoice does not double-credit.
  4. Notes — free text retained on the payment row for your records.
Figure 23.7 — The Record-payment dialog (a received wire).
  1. Select the invoice and click Record payment.
  2. Enter the amount received (pre-filled with the outstanding total; reduce it for a part payment).
  3. Choose the methodwire for a bank transfer, manual_credit for a keyed credit.
  4. Add the wire reference and any notes, then Record payment.
  5. The invoice updates immediately: amount_paid rises and the status becomes partial or, when fully covered, paid (with the paid timestamp stamped). The ledger shows a matching negative payment row.

To post a credit / adjustment instead, click Credit / adjustment, choose the type (credit / adjustment / refund), enter the amount and a reason, and target either an account or a specific invoice (exactly one). The result is a negative ledger row that reduces the party’s balance — visible immediately on their statement.

Credit vs. adjustment vs. refund — pick the right word

All three write the same negative row, but the type is preserved on the statement so finance can tell them apart. Use credit for goodwill (a service credit you grant), adjustment to correct a billing error, and refund when money actually went back out. The reason text you enter is what appears on the statement, so be specific.

Mixed settlement is fine

An invoice can be settled partly online and partly by wire — record a part wire, then send the remaining balance to Stripe checkout (or vice-versa). The invoice tracks amount_paid against total regardless of how each slice arrived, flipping to paid only when fully covered.

23.6 Worked example — a full May billing cycle for a reseller’s tenant

Let us run one cycle end to end. Acme Comms is a reseller. Its tenant Northwind Retail made outbound calls in May. We will price them on both cards, close the period as the platform, and settle Acme’s wholesale bill by wire. Assume the platform Standard Wholesale 2026 card and Acme’s retail card are both active, with these voice entries:

Wholesale (platform) · prefix 65
$0.008 / min · increment 60s · no allowance
Retail (Acme) · prefix 65
$0.014 / min · increment 60s · no allowance

Northwind’s May usage: 1,000 answered, normally-cleared outbound minutes to Singapore (+65…). (For clarity we use round minutes; the rater bills ceil(billsec/60) per call, so real totals are the sum of per-call rounding.)

23.6.1 Step 1 — usage is rated into charges

As the CDRs land and the rater runs, each Singapore call is matched to the prefix-65 entry on both cards. Two charges are posted per relationship:

Table 23.3 — The two charge legs for Northwind’s May voice (1,000 min to +65)
LegPayer (account_id)CounterpartyComputationCharge
RetailNorthwind (tenant)Acme (reseller)1,000 × $0.014$14.00
WholesaleAcme (reseller)CloudCX (platform)1,000 × $0.008$8.00

The moment these post, the ledger already reflects them: Northwind’s balance rises by $14.00 (it owes Acme) and Acme’s balance rises by $8.00 (it owes CloudCX). No invoice exists yet — the balance moved on rating, not on invoicing. Acme’s margin on this traffic is the difference, $6.00, which the margin report (Billing II) surfaces for you automatically.

23.6.2 Step 2 — close the May period (platform issues Acme’s wholesale invoice)

On 1 June you run Billing → Invoices → Generate invoices for 2026-05-01 to 2026-05-31, due-days 14, leaving the account list blank. CloudCX groups Acme’s pending wholesale charges (the $8.00 line, plus any other May wholesale) into INV-PLATFORM-2026-000123, status open, total $8.00 (no tax jurisdiction set), due 14 June. The pulled-in charge flips to invoiced. Acme’s balance is unchanged at $8.00 — the invoice only stated what was already there.

Separately, in Acme’s own console, Acme runs their close to issue Northwind’s retail invoice for the $14.00 — issuer Acme, numbered in Acme’s own sequence, collected into Acme’s Stripe.

23.6.3 Step 3 — Acme pays CloudCX by wire

Acme settles by bank transfer. You open INV-PLATFORM-2026-000123, click Record payment, enter $8.00, method wire, reference the SWIFT ref, and save. The invoice flips to paid (paid-at stamped), and a negative payment row of −$8.00 posts to Acme’s ledger — taking Acme’s balance back to $0.00. The cycle is closed.

The May cycle on Acme’s ledger
Rated (May)Wholesale voice charge posted — balance moves up+ $8.00 → bal $8.00
Invoiced (1 Jun)Charge grouped into INV-…-000123 — balance unchanged$0.00 → bal $8.00
Settled (12 Jun)Wire recorded against the invoice — balance paid down− $8.00 → bal $0.00

Note the middle row: invoicing moved the charge status (pending→invoiced) but not the balance. Only the charge (up) and the payment (down) touched the balance.

Try it — price, invoice and settle a tenant from scratch

In a non-production tenant, run the whole core cycle yourself:

  1. Build a wholesale card. In Billing → Wholesale rate cards, create Lab Wholesale (USD, active). Add a catch-all voice entry at 0.030/min (increment 60), and a specific voice entry for prefix 1 (North America) at 0.011/min.
  2. Generate some billable usage. Place (or simulate) a few outbound, answered calls from a direct tenant to a +1… number, plus one to an unmatched destination. Wait for the next rating pass.
  3. Check the statement. Open the tenant’s account row. Confirm the +1 calls are charged at the prefix-1 rate, the others at the catch-all, and the tenant’s balance has risen by the total. Note that no invoice exists yet.
  4. Close the period. Run Generate invoices for this month, blank account list. Confirm one open invoice appears, its total equals the charges you saw, and the charges are now invoiced. Re-run the close and confirm no duplicate appears.
  5. Settle it two ways. Record a part wire for half the total (watch it go partial), then either record the rest as a second wire or, if you have a Stripe test key configured, click Pay online and follow the URL. Confirm the invoice reaches paid and the balance returns to $0.00.
  6. Prove the model. Post a small credit (reason “lab goodwill”) to the account and watch the balance go negative — the account is now in credit. Read the statement top to bottom: charge (up), payment (down), credit (down).

What you have proven: the rate card prices usage, rating moves the balance, an invoice merely states the charges, and only payments/credits pay the balance down. That is the whole core billing path.

23.7 Troubleshooting & reference

Table 23.4 — Common billing symptoms and where to look
SymptomLikely causeFix
A whole class of calls/messages is freeNo matching rate-card entry (often a missing catch-all), or the card is not active.Add the catch-all entry; confirm the card is active. The rater logs the miss once per scope/channel.
“Pay online” returns a 409The issuer has no enabled Stripe config.Configure (or enable) Stripe under Platform payment configuration — or settle manually.
Checkout completes but the invoice stays openWebhook secret not stored, or the endpoint URL/events are wrong — verification fails closed.Store the whsec_… secret; point Stripe at /webhooks/stripe/platform/- with the three success events.
An invoice total and the balance disagreeBy design — invoicing does not move the balance; only charges/payments/credits do.Read the statement: the balance is the net of all charges and payments, not of one invoice.
Re-running a close worried youIt is idempotent — invoiced charges are not re-grouped.Safe to re-run; no duplicate invoices are created.
Edited a rate but yesterday wasn’t re-billedCharges are immutable; rate edits are forward-only.Post a credit/adjustment to correct past charges; the new rate applies to future rating.

23.7.1 Glossary

Billing account per party
One ledger account per platform / reseller / tenant, carrying that party’s balance, currency and mode. Auto-created on first use.
Journal / statement immutable
The append-only list of signed money events with running balance-after snapshots. The authoritative history; the account balance is its cache.
Charge pending → invoiced
A priced line for one usage event, with a payer and a counterparty. Idempotent on its external_id; moves the balance when rated.
Rate card wholesale / retail
A scoped set of per-prefix rates. Platform cards are wholesale; reseller cards are retail. The rater uses the newest active card and longest-prefix match.
Invoice a statement
A periodic grouping of an account’s pending charges into a numbered document. Does not move the balance.
Settlement online / manual
Collecting an invoice: online via the issuer’s Stripe (credited only on the verified webhook), or manually by recording a wire / posting a credit.
Bring-your-own Stripe per-entity
Each issuer collects into its own Stripe account; a reseller’s tenants pay the reseller, the platform’s customers pay the platform.
Next: Billing II

You now have the core path: model → rate cards → invoices → settlement. The next chapter, Billing II, covers the panels you saw lower in the workspace — prepaid & credit control, tax & FX, dunning (reminders, suspend/unsuspend) and the finance reports (revenue, margin per reseller, receivables aging, reconciliation).

CloudCX · Administrator & Training ManualCh. 23 — Billing I
CloudCXCloudCX Administrator & Training Manual
Ch. 24 · Billing II
Chapter 24

Billing II — Prepaid, Postpaid, Tax/FX, Dunning & Reports

Chapter 23 set up the money machine: billing accounts, the platform Stripe configuration, wholesale rate cards and the invoices CloudCX issues. This chapter is about controlling spend and getting paid. You will switch accounts between postpaid and prepaid, set credit limits and low-balance alerts, record top-ups, apply tax by jurisdiction and FX rates for cross-currency settlement, chase overdue invoices with dunning, and read the finance reports — revenue, margin, aging and a three-way reconciliation that proves the ledger is sound.

Where these screens live

Everything in this chapter is on the platform RevenueBilling view at admin.cloudcx.app. It is one long page; the panels we cover here — Prepaid & credit control, Tax & FX configuration, Dunning & collections and Billing reports — sit below the accounts, Stripe and invoice panels from Chapter 23. Resellers see scoped versions of the same controls for their own book in the reseller portal (§24.7).

24.1 The golden rule: money moves in exactly one place

Before any screen, internalise one invariant that the whole billing system depends on. The balance ledger is signed in “what the party owes us” terms. A positive balance means the party owes CloudCX money; a negative balance means CloudCX holds their credit (we owe them service). Every figure on every screen in this chapter follows from that one convention.

The sign convention — charges push the balance up, payments push it down
Top-up / payment
negative row
balance ↓
more credit
·
balance ↑
owes us more
Usage / charge
positive row

So a prepaid account that has deposited funds carries a negative balance — that is its stored credit. As the customer makes calls and sends messages, charges raise the balance back toward zero. The spendable wallet is therefore:

# available credit = how much they can still spend
available_credit = max(0, -balance)

balance = -50  →  available_credit = 50   # deposited 50, spent nothing
balance = -12  →  available_credit = 12   # spent 38 of the 50
balance =   0  →  available_credit =  0   # exhausted
balance =  +3  →  available_credit =  0   # slightly overspent; clamped to 0
One door for money

No screen in this chapter moves the balance directly. Setting a billing mode, a credit limit, a tax rate or an FX rate only stores policy. Recording a top-up posts a payment, and a payment is the only kind of money movement an operator initiates here — it always flows through the accounting service so the journal and the cached balance can never disagree. Reconciliation (§24.6) exists to prove exactly that.

24.2 Postpaid vs prepaid — the two billing modes

Every billing account is in one of two modes. The default for every account — resellers, tenants, and the platform — is postpaid with no credit limit, which means “never blocked”. You opt an account into tighter control deliberately.

Postpaid — invoice then collect

The customer uses the service freely; CloudCX bills the accumulated usage on an invoice and collects afterwards. Optionally cap exposure with a credit limit: once the balance reaches the limit, new billable actions are blocked until they pay down.

Prepaid — collect then spend

The customer deposits funds first (a top-up); usage draws that wallet down. When available credit reaches zero, the next billable action is blocked. A low-balance threshold warns them before they run dry.

Table 24.1 — What each mode enforces, and what blocks
ModeCredit limitBehaviourBlocks usage when…
PostpaidnoneDefault. Use freely, billed in arrears.Never (unconditional allow)
Postpaidset, e.g. 1,000Use up to the ceiling, billed in arrears.balance ≥ credit_limit
Prepaidn/aSpend deposited credit only.available_credit < cost (wallet empty)
Choosing a mode

Use prepaid for new resellers with no payment history, for high-risk destinations, or wherever you want a hard cap with no collections risk. Use postpaid with a credit limit for trusted partners who need uninterrupted service but a sensible exposure ceiling. Leave well-established, contracted customers on plain postpaid.

CloudCX · Administrator & Training ManualChapter 24 · Billing II
CloudCXCloudCX Administrator & Training Manual
Ch. 24 · Billing II

24.3 Prepaid & credit control

The Prepaid & credit control panel is where you set an account’s mode, limit and alert level, and record top-ups. You work one account at a time: pick it from the account selector and the panel fills in with that account’s live wallet view.

admin.cloudcx.app
Revenue
Billing
Rate cards
Invoices9
Tenancy
Resellers12
Tenants86
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Billing// 03 — platform billing
PRODAO
Phase 5b

Prepaid & credit control

Switch mode, set a credit limit & low-balance threshold, and record a top-up.

Account Acme Comms (reseller) · EUR
Balance
−€420.00
positive = owes us
Billing mode
Prepaid
collect then spend
Available credit
€420.00
spendable wallet
Low-balance alert
€50.00
prepaid warn level
Billing modePrepaid (collect then spend)
Credit limit (blank = no limit)
Low-balance threshold (prepaid)50.00
Save credit settings
Record a top-up
Amount500.00
Manual methodWire transfer
Referencewire ref / cheque no.
Record manual top-upTop-up online (Stripe)
1
2
3
4
5
6
  1. Billing — the active item under the Revenue nav group; this whole chapter lives in this one view.
  2. Account selector — choose any reseller, tenant or the platform account. Every figure below refreshes for it.
  3. Balance — the raw ledger figure. −€420.00 is credit (they owe us nothing; we hold €420 for them).
  4. Available credit / Low-balance alert — the derived wallet (max(0, −balance)) and the warn level.
  5. Credit settings — mode, credit limit and threshold. Save stores policy only; it never moves money.
  6. Record a top-upmanual (a wire you received) or online (Stripe checkout). This is the one money move here.
The Prepaid & credit control panel for a prepaid reseller account holding €420 of stored credit.

24.3.1 Setting an account’s mode, limit and threshold

  1. Open the panel. Go to RevenueBilling and scroll to Prepaid & credit control.
  2. Pick the account. Choose it from the Account selector. The four KPI tiles (Balance, Billing mode, Credit limit, Low-balance alert) populate from the live wallet view.
  3. Choose the billing mode. Set Billing mode to Prepaid or Postpaid.
  4. Set the controls. For postpaid, type a Credit limit (leave blank for none). For prepaid, type a Low-balance threshold — the available-credit level at which the account is warned.
  5. Save. Click Save credit settings. Only the fields you changed are written; clearing a field removes that ceiling/threshold (back to “no enforcement”).
Flipping to prepaid takes effect immediately

If you switch an account to prepaid while its balance is zero or positive, its available credit is already zero — so the usage gate (§24.4) will block its next call or message right away. Always confirm the account has been topped up (a negative balance) before, or immediately after, moving it to prepaid. Flipping back to postpaid and clearing any limit restores the default always-allow behaviour.

24.3.2 Recording a top-up

A top-up deposits credit. Because a deposit is a payment, it lowers the balance (adds available credit). There are two ways to record one:

Manual top-upmethod: wire | manual_credit
You received the money out-of-band (a bank wire, or a keyed goodwill/opening credit). Enter the Amount, pick the Manual method, and add a Reference (the wire reference or cheque number). The credit posts instantly. The reference also makes the top-up idempotent — re-submitting the same reference against the same account will not double-credit.
Online top-upStripe Checkout
Click Top-up online (Stripe) to open a hosted Stripe checkout for the amount. The ledger is credited only when Stripe confirms the payment via its webhook — not when the page opens. Requires an enabled Stripe configuration for the owner; otherwise use a manual top-up.
  1. Enter the amount. Type the deposit in the account’s currency in the Amount field of Record a top-up.
  2. Manual path. Pick the Manual method (Wire transfer / Manual credit), type the Reference, then click Record manual top-up. The Balance and Available-credit tiles update at once.
  3. Online path. Click Top-up online (Stripe). CloudCX returns a hosted payment URL; the customer pays there and the credit appears once Stripe’s webhook confirms.
Why online top-ups appear a moment later

The online button only starts a checkout. CloudCX never credits the ledger on a button click — that would let an unpaid session add credit. The existing per-entity Stripe webhook credits the account by its account_id the instant Stripe reports the payment succeeded. If a manual top-up returns “not configured”, the owner has no enabled Stripe; record a manual wire instead.

CloudCX · Administrator & Training ManualChapter 24 · Billing II
CloudCXCloudCX Administrator & Training Manual
Ch. 24 · Billing II

24.4 The usage gate — how spend control actually blocks

Credit limits and prepaid wallets are enforced by a single real-time check that sits in front of every billable action — a placed call, a sent SMS or WhatsApp message. The gate answers one question: may this tenant incur a charge of about this cost right now? It reads the balance; it never moves money. Understanding its decision table tells you exactly what each setting on the previous panel does.

The gate decision — evaluated before each billable action
No account yetNothing provisioned to enforce against.ALLOW
Postpaid, no limitThe default for every account — zero behaviour change.ALLOW
Postpaid + limitbalance < credit_limit → allow; balance ≥ credit_limit → block.CHECK
Prepaidavailable_credit ≥ cost → allow; otherwise block (“balance exhausted”).CHECK
Any error / DB hiccupA spend guard must never take down calling for the platform.ALLOW
Fail-open by design

The gate is a spend guard, not a security control. Any failure — a missing account, a transient database error — resolves to allow. The worst case is a little overspend the ledger still records and a later invoice still collects; the worst case of failing closed would be a platform-wide calling outage. CloudCX chooses fail-open deliberately.

Idle-safe

Because a brand-new account is postpaid with no limit, wiring the gate into the call/SMS/WhatsApp paths changed nothing for the existing fleet. Only two populations are ever blocked: a prepaid account whose wallet is below the action’s cost, and a postpaid account an operator explicitly gave a limit and has now reached.

When the gate blocks, the API surfaces a 402 Payment Required with a reason — prepaid balance exhausted or credit limit reached — and the action is refused before any charge is posted. The Available credit tile and the live can spend flag on the panel show the same verdict the gate uses, so what you see is what callers get.

24.4.1 Low-balance alerts

The low-balance threshold does not block — it warns. A periodic sweep examines prepaid accounts that have an explicit threshold set, and when available credit falls to or below the threshold it emits a prepaid.low_balance event and a best-effort email to the account’s billing address. Accounts with no threshold are skipped entirely, so the sweep is silent until you opt an account in.

A threshold of zero is honoured

Setting a low-balance threshold of 0 means “only warn me once the wallet is fully exhausted” — that is different from leaving the threshold blank (NULL), which opts the account out of the sweep altogether. Use a small positive value (for example €50) so the customer is warned with time to top up.

24.5 Worked example — onboarding a prepaid reseller

A new partner, Acme Comms, has no payment history, so you put them on prepaid with a €50 low-balance alert and seed their wallet with a €500 wire they have already sent.

Scenario

Acme’s billing account currency is EUR. Its balance is €0.00 (brand new, default postpaid). You received a €500 wire, reference WIRE-AC-0418.

  1. Select the account. In Prepaid & credit control, choose Acme Comms (reseller) · EUR. Balance reads €0.00, mode Postpaid, available credit €0.00.
  2. Record the wire first. Under Record a top-up: Amount 500.00, Manual method Wire transfer, Reference WIRE-AC-0418. Click Record manual top-up. The balance moves to −€500.00 and available credit to €500.00.
  3. Now switch to prepaid. Set Billing mode to Prepaid and Low-balance threshold to 50.00. Click Save credit settings. (Topping up before flipping the mode means service is never interrupted.)
  4. Verify the wallet. The tiles now read Balance −€500.00, Mode Prepaid, Available credit €500.00, Low-balance alert €50.00. The live can spend flag is true.

Over the next weeks Acme’s usage draws the wallet down. Here is how the balance and gate verdict track:

Table 24.2 — Acme Comms prepaid wallet over time
EventLedger balanceAvailable creditLow-bal alert?Gate verdict
Opening (postpaid)€0.00€0.00allow (no limit)
Wire €500 recorded−€500.00€500.00noallow
Switched to prepaid−€500.00€500.00noallow
Usage €455 charged−€45.00€45.00yes — at/below €50allow
Usage €45 more€0.00€0.00yesblock — exhausted
Top-up €300−€300.00€300.00noallow

Notice the low-balance alert fired at €45 (at or below the €50 threshold) — while calls were still allowed — giving Acme time to top up. The block only happened at true exhaustion, and the next top-up restored service instantly.

CloudCX · Administrator & Training ManualChapter 24 · Billing II
CloudCXCloudCX Administrator & Training Manual
Ch. 24 · Billing II

24.6 Tax & FX configuration

Two rate tables feed the invoicing engine: tax rates (the VAT / GST / sales tax applied to an invoice subtotal, keyed by the billed account’s jurisdiction) and FX rates (admin-set exchange rates used to convert a charge whose currency differs from the invoice currency). Both are idle-safe: with no tax rows every invoice’s tax is zero, and with everything single-currency no FX conversion ever runs.

admin.cloudcx.app
Revenue
Billing
Rate cards
Invoices9
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Billing// tax & FX
PRODAO
Phase 5c

Tax & FX configuration

Tax rates

+ Add tax rate
JurisdictionNameRate %Status
SGGST9.000 Activeedit · delete
GBVAT20.000 Activeedit · delete
US-CASales tax7.250 Activeedit · delete

FX rates

+ Add FX rate
FromToRateUpdated
EURSGD1.4521002026-06-09delete
USDSGD1.3500002026-06-09delete
1
2
3
4
5
  1. Add tax rate — reveals the jurisdiction / name / rate form. One canonical row per jurisdiction (re-adding replaces it).
  2. Jurisdiction — the code matched against an account’s jurisdiction (SG, GB, US-CA…), upper-cased server-side.
  3. Rate % — shown as a percentage; stored internally as a fraction (9% = 0.09). Range 0–100%.
  4. Add FX rate — a base→quote pair for a day. Self-pairs (EUR→EUR) are always 1.0 and rejected.
  5. Rate1 from = rate × to. The newest rate on or before a conversion date is the one applied.
Tax rates by jurisdiction and admin-set FX rates — both feed the rating & invoicing engine.

24.6.1 Adding a tax rate

  1. Open the form. In Tax & FX configuration, click + Add tax rate.
  2. Enter the jurisdiction. Type the code (SG, GB, US-CA…). It is the key the invoicing path matches against the billed account’s jurisdiction.
  3. Name and rate. Give it a label (GST, VAT, Sales tax) and the rate as a percent (e.g. 9 for 9%).
  4. Save. If a row already exists for that jurisdiction it is replaced; otherwise a new one is created. Toggle Active off to keep a row but stop applying it.
Where the rate gets applied — and exemptions

An account’s jurisdiction and tax-exempt flag live on its tax profile (set on the account’s config — admins per account, resellers per customer). When an invoice is generated, the engine looks up the active tax rate for that account’s jurisdiction and applies it to the subtotal. No jurisdiction set, no matching/active rate, or tax-exempt → tax is zero. Editing or deleting a rate never changes invoices already issued — their tax is already recorded.

24.6.2 Adding an FX rate

FX rates only matter when a charge’s currency differs from the invoice currency — for example a reseller billed in SGD whose platform charges accrued in EUR. The rate is directional: 1 base = rate × quote.

  1. Open the form. Click + Add FX rate under FX rates.
  2. Enter the pair. From currency (base) and To currency (quote), e.g. EURSGD. They must differ.
  3. Enter the rate. A positive number, e.g. 1.4521 meaning €1 = S$1.4521. The effective date defaults to today.
  4. Save. A pair already set for that day is replaced. Add a new row whenever the rate moves — conversions use the newest rate dated on or before the conversion date.
Preview a conversion before you trust it

There is an ad-hoc convert preview that runs the exact same engine invoicing uses: give it an amount, a from-currency and a to-currency and it returns the converted amount and the rate applied. A same-currency request returns the amount unchanged at rate 1.0; if no rate is available it returns the amount unchanged and flags converted = false so you know a rate is missing rather than silently mis-billing.

CloudCX · Administrator & Training ManualChapter 24 · Billing II
CloudCXCloudCX Administrator & Training Manual
Ch. 24 · Billing II

24.7 Dunning & collections

Dunning is the collections lifecycle for postpaid accounts: chasing invoices that have gone past their due date and, ultimately, suspending a delinquent account’s service until it pays. A periodic sweep does the work automatically, but the panel lets you see arrears at a glance and act on any account by hand.

The dunning escalation ladder — driven by the age of the oldest overdue invoice
Open
not yet due
Overdue
past due_date
Reminder 1
≥ 7 days
Reminder 2
≥ 14 days
Suspended
≥ 30 days

Each sweep does three guarded things, all idle-safe (with nothing overdue it is a pure no-op):

admin.cloudcx.app
Revenue
Billing
Invoices9
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Billing// dunning & collections
PRODAO
Phase 5c

Dunning & collections

Invoices past due. Send a reminder, suspend a delinquent account, or unsuspend on payment.

Run dunning now
InvoiceAccountDueDays lateOutstandingActions
ININV-2041 Northwind Ltd suspended 2026-05-0838£1,240.00 Remind Unsuspend
ININV-2055 Helios SG 2026-05-2917S$880.00 Remind Suspend
ININV-2061 Acme Comms 2026-06-069€310.00 Remind Suspend
1
2
3
4
  1. Run dunning now — triggers the same sweep on demand: mark overdue, escalate reminders, suspend. Returns counters.
  2. Days late — days past due_date as of today. This drives the escalation stage (7 / 14 / 30).
  3. Outstanding — the still-unpaid amount on the invoice (total − amount paid), in the invoice currency.
  4. Per-account actionsRemind records a reminder & advances the level; Suspend / Unsuspend toggle service.
The Dunning & collections panel: every overdue invoice annotated with days late and the manual actions.

24.7.1 Running a sweep and acting on an account

  1. Review the arrears. Open Dunning & collections. The table lists every overdue invoice (and any still-open invoice already past due), newest due date first, annotated with Days late and Outstanding.
  2. Run the sweep (optional). Click Run dunning now to apply the escalation immediately rather than waiting for the periodic task. It reports how many invoices it marked overdue, how many reminders it sent, and how many accounts it suspended.
  3. Send a manual reminder. On a row, click Remind to record a payment reminder and advance that account’s dunning level by one stage (capped below suspension). An email goes to the billing address.
  4. Suspend a delinquent account. Click Suspend to stop service: the dunning flag is set and the account status becomes suspended. The usage gate immediately refuses new billable actions.
  5. Unsuspend on payment. Once they settle, click Unsuspend. The flag clears, the status returns to active, and the dunning level resets so collections can re-escalate cleanly if they fall behind again.
Suspension stops all billable usage

Suspending an account is a blunt instrument: it blocks every outbound call and message for that tenant until you unsuspend. Reserve Suspend for genuine non-payment after reminders have gone unanswered. Manual and automatic dunning share the exact same state transitions, so a manual suspend is indistinguishable from the day-30 auto-suspend — and Unsuspend only re-activates accounts that dunning suspended, never one you closed by hand.

CloudCX · Administrator & Training ManualChapter 24 · Billing II
CloudCXCloudCX Administrator & Training Manual
Ch. 24 · Billing II

24.8 Billing reports

The reports panel is the finance read-out over the ledger. Nothing here moves money — every figure is a scoped aggregate. As a platform admin you see the whole book; a reseller sees only its own account and its tenants’. Four reports, each exportable as CSV: revenue, per-reseller margin, receivables aging, and reconciliation mismatches.

admin.cloudcx.app
Revenue
Billing
Invoices9
ALL SYSTEMS OPERATIONAL
v4.8 · eu-west
Billing// billing reports
PRODAO
Phase 5d

Billing reports

Charged (wholesale)
€48,210
period to date
Invoiced
€46,900
incl. tax
Paid
€41,300
collected
Outstanding
€5,600
receivable

Revenue & margin per reseller

Revenue CSV
ResellerRetailWholesaleMargin
ACAcme Comms€18,400€11,900 €6,500
NWNorthwind€9,250€6,800 €2,450

Receivables aging

Aging CSV
BucketCurrent1–3031–6061–9090+
Total outstanding€2,100€2,050€890€310€250
1
2
3
4
  1. Revenue summary — charged / invoiced / paid / outstanding for the period. Charges are grouped by type (channel) underneath.
  2. Margin per reseller — retail (its tenants’ usage) vs wholesale (what the reseller owes the platform); margin is the difference.
  3. CSV export — every report streams a finance-ready CSV download for the same scope and period.
  4. Aging buckets — outstanding receivables split by how overdue they are: current / 1–30 / 31–60 / 61–90 / 90+ days.
The Billing reports panel: revenue & per-reseller margin, and receivables aging, each exportable as CSV.

24.8.1 The four reports

Revenue

Charges grouped by type/channel (voice, sms, whatsapp, seat, did_rental, setup, one_off) plus the invoiced / paid / outstanding trio. The platform view is wholesale revenue; a reseller’s view is its retail revenue.

Margin

Per reseller: retail (its tenants were charged) minus wholesale (the reseller was charged by the platform) = billed margin; the paid-side difference is cash margin. Sorted largest margin first.

Aging

Outstanding invoice balances bucketed by overdue age relative to due_date — current / 1–30 / 31–60 / 61–90 / 90+. Per account plus a grand total, as of a chosen date.

Reconciliation

A finance audit that returns only the mismatches (§24.9). On a healthy ledger it returns ok with no issues. Admin-only — it scans the whole book.

Each report takes an optional from/to date window (reversed bounds are swapped for you), and accepts ?format=csv (or the explicit .csv button) for a streaming download with an attachment filename. Idle-safe everywhere: no data yields zeroed rollups, an empty aging report, and CSVs containing just their header row.

Retail, wholesale and margin in one sentence

Retail is what a reseller’s tenants owe the reseller; wholesale is what the reseller owes CloudCX; margin is the reseller’s gross profit between the two. The platform margin report computes this for every reseller in one pass, so finance can see the whole partner book’s profitability at a glance.

CloudCX · Administrator & Training ManualChapter 24 · Billing II
CloudCXCloudCX Administrator & Training Manual
Ch. 24 · Billing II

24.9 Three-way reconciliation

Reconciliation is the proof that the money is right. It runs three independent audits that must always hold on a healthy ledger and returns only the discrepancies that exceed a small rounding tolerance (0.01). An empty issues list and ok means everything ties out.

Table 24.3 — The three reconciliation checks
CheckWhat it assertsCatches
Balance integrityFor each account, the signed journal sums to the cached balance, and the source rows reproduce it: Σ charges − Σ payments − Σ credits ≈ balance.A source row that never produced its journal row (or vice-versa); a corrupted balance cache.
Invoice integrityPer invoice: total ≈ subtotal + tax; subtotal ≈ the sum of its linked charges; amount_paid ≈ the payments recorded against it.A mis-totalled invoice; a payment applied to the wrong invoice; a header that drifted from its lines.
Stripe integrityPer account, the sum of succeeded Stripe payments equals the sum of the journal payment rows that back them.A Stripe webhook credit that recorded a payment but never wrote its ledger row (or wrote a mismatched one).

Each mismatch names the failed check, the offending row (an account or invoice), the expected and actual figures and their delta, so finance can drill straight to the cause. All money is computed in exact decimal — never floating point — matching the six-decimal ledger columns.

admin.cloudcx.app
Phase 5d

Reconciliation mismatches

3,481 accounts & 612 invoices checked · tolerance 0.01

Reconciliation CSV
CheckRefExpectedActualDelta
stripe_payments_vs_ledgeracct · Helios SGS$880.00S$0.00−880.00
invoice_total_vs_subtotal_plus_taxINV-2041£1,240.00£1,239.40−0.60
Reading a mismatch

The first row says a S$880 Stripe payment was recorded but no backing ledger row exists — a webhook credit that didn’t reach the journal. The second is a 60-cent invoice header drift worth a closer look.

Reconciliation mismatches — the audit returns only the rows that failed a check, with the delta to fix. A healthy ledger shows this panel empty with an ok badge; any row here is a real finance issue above the rounding tolerance.

24.9.1 Worked example — investigating a Stripe mismatch

  1. Run reconciliation. Open Billing reportsReconciliation mismatches. It reports ok=false with one issue: stripe_payments_vs_ledger on the Helios SG account, expected S$880, actual S$0, delta −880.
  2. Interpret it. A Stripe payment of S$880 was recorded against the account, but the journal has no matching payment row — so the cached balance is S$880 higher than it should be. The customer paid; the ledger never learned.
  3. Find the payment. Open the Helios SG account statement (Chapter 23) and locate the succeeded Stripe payment. Confirm in the Stripe dashboard that the charge cleared.
  4. Correct the ledger. Replay the webhook (the supported path) so the missing journal payment row is written and the balance corrects. Re-run reconciliation — the issue should clear and the panel return to ok.
  5. Record the resolution. Note the cause and fix; these actions are auditable. If the delta were a tiny header rounding figure instead, you would patch the invoice rather than the journal.
Make reconciliation a routine

Run reconciliation on a schedule (a month-end close at minimum) and after any incident touching payments — a Stripe outage, a manual ledger edit, a migration. A clean reconciliation is the single best signal that the billing system is trustworthy; catching a mismatch early, before it compounds across invoices, is far cheaper than unwinding it later.

24.10 Resellers manage their own book

Everything in this chapter has a reseller-scoped counterpart in the reseller portal. A reseller can read and set its own account’s mode, limit and threshold; manage each of its customers’ billing (mode, limit, threshold, tax jurisdiction); record top-ups to its own or a customer’s account; and pull its own revenue, usage, margin and aging reports. The scoping is strict: a reseller can only ever touch its own account and its own tenants — a request for any other party returns 404, so it cannot even probe what exists outside its book. Platform-wide reconciliation stays admin-only.

Try it — run the full prepaid & collections loop on a sandbox account

On a non-production tenant in the admin sandbox, drive an account through its whole lifecycle:

  1. Provision prepaid. In Prepaid & credit control, pick a sandbox account, record a manual top-up of 100.00 (method Manual credit, reference TRY-01), then switch the mode to Prepaid with a low-balance threshold of 20.00. Confirm Balance reads −100.00 and Available credit 100.00.
  2. Test idempotency. Re-submit the same top-up with reference TRY-01. Confirm the balance does not change — the reference made it idempotent.
  3. Set tax. In Tax & FX configuration, add a tax rate for jurisdiction SG, name GST, rate 9. Add an FX rate EURSGD at 1.45, then use the convert preview to convert €100 to SGD and confirm you get S$145.
  4. Force arrears. On a postpaid sandbox account with a past-due open invoice, open Dunning & collections and click Run dunning now. Confirm the invoice flips to overdue and note the returned counters.
  5. Suspend & restore. Click Suspend on that account, confirm its status becomes suspended, then Unsuspend and confirm it returns to active.
  6. Prove it ties out. Open Billing reportsReconciliation and confirm ok=true with no issues — every top-up, charge and status change you made still reconciles.

Success criteria: the wallet reflects exactly one €100 credit (not two), GST appears on a newly generated SG invoice, the overdue invoice escalated, suspend/unsuspend toggled the status cleanly, and reconciliation is green.

Chapter recap

You can now control spend two ways — prepaid wallets and postpaid credit limits, both enforced by the fail-open usage gate — record manual and online top-ups, apply tax by jurisdiction and FX for cross-currency settlement, chase overdue accounts through the dunning ladder to suspension and back, read the revenue / margin / aging reports, and prove the ledger with a three-way reconciliation. Chapter 25 moves from money to quality: service levels, SLAs and the analytics that measure them.

CloudCX · Administrator & Training ManualChapter 24 · Billing II
CloudCXCloudCX Administrator & Training Manual
Ch. 25 · Security & administration
Chapter 25

Security and administration — credentials, IP rules, MFA/SSO, licensing, settings

This chapter is the platform-administrator’s controls for trust and global configuration: the encrypted credentials vault that holds CloudCX’s shared provider and AI keys, the IP allow/deny firewall in front of the API, the password policy and two-factor authentication, the single sign-on scaffold, the licensing pool, and the platform-wide defaults. These live in the Governance and Platform groups of the admin console. Every screen here is admin-gated, every secret is encrypted at rest, and almost every control is additive — it changes nothing until you deliberately turn it on.

25.1 The security surface at a glance

Five admin views make up this chapter, plus one self-service control that lives on your own account. They are deliberately spread across two sidebar groups because they serve two different jobs — connecting the platform to the outside world (Platform) and defending and governing it (Governance).

What lives where, and what it protects
Platform CredentialsCloudCX’s shared SMS / WhatsApp / email / AI keys, encrypted with Fernet. Used by any tenant whose reseller has not brought its own.Platform
Security — Password policyMinimum length and character-class rules applied to new passwords.Governance
Security — IP access rulesCIDR allow/deny rules enforced in front of the API by middleware.Governance
Security — Two-factor (2FA)TOTP for your own signed-in account; opt-in, default off.Governance
Security — SSO / OIDCExternal identity-provider config (scaffold; sign-in flow not yet live).Governance
LicensingLicensed seat / customer pool allocated to resellers, and consumption.Governance
SettingsPlatform defaults — timezone, region, recording, retention, maintenance.Governance

25.1.1 The one secret that stays outside the database

Everything secret in this chapter — provider tokens, the SSO client secret, your 2FA seed — is encrypted with Fernet (AES-128-CBC + HMAC) before it touches the database. The encryption key itself, BYOND_CREDS_KEY, is the one root secret that lives in the server environment and never in the encrypted store. This split is what makes a database dump useless on its own.

How a secret is stored and read back
You type
a token
admin console
Fernet
encrypt
BYOND_CREDS_KEY
Ciphertext
row
DB column
Fernet
decrypt
on the send path
Used to
send
never returned
Write requires the key; reads fail safe

Encrypting a new secret requires BYOND_CREDS_KEY on the server. If it is missing or malformed, the write is rejected with 503 rather than ever storing plaintext. Reading a secret never crashes: a missing/invalid key or undecryptable ciphertext quietly degrades to “not configured,” so a key problem can take a channel idle but can never 500 a live send.

Generate the root key once

The key is a urlsafe-base64 32-byte Fernet key. Generate it once and set it in the server environment:

# generate the Fernet master key (run once, store in the server env)
$ python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
qZ3...redacted...nB8=
$ export BYOND_CREDS_KEY=qZ3...redacted...nB8=
Rotating the key is destructive

Changing BYOND_CREDS_KEY makes every previously-stored secret undecryptable — channels, the SSO secret and every enrolled 2FA seed silently revert to “not configured.” Treat rotation as a re-key project: plan to re-enter every credential and have users re-enrol 2FA afterwards. Keep the key in a secrets manager, never in the repo.

CloudCX · Administrator & Training ManualCh. 25 · Security & administration
CloudCXCloudCX Administrator & Training Manual
Ch. 25 · Credentials vault

25.2 The platform credentials vault

Open PlatformPlatform Credentials. This is where you store CloudCX’s own shared connectivity keys — the SMS, WhatsApp, email and AI credentials “bound to CloudCX” that are used by every tenant whose reseller has not brought its own credentials for that channel. Each provider is a card; cards never show stored values, only whether the provider is Configured and the fields it expects.

admin.cloudcx.app
Platform
Call Routing
Channels
Platform Credentials
Governance
Security
Licensing
Settings
ALL SYSTEMS OPERATIONAL
v4.8 · ap-southeast
Platform Credentials// shared provider & AI keys · encrypted
PRODAO
Connectivity & AI keys

Platform Credentials

CloudCX’s shared connectivity credentials, used for tenants that don’t bring their own. Stored encrypted.

Refresh
ENCRYPTED Secrets are stored Fernet-encrypted; values are never shown after saving.

SMS outbound / 2-way text

Configured ✓
Account SIDleave blank to keep current
Auth token••••••••••••
From number+15551234567
SaveRemove

AI LLM provider

Not set
API keysk-ant-…
Model(platform default)
Optional. Leave blank to use the platform default model.
SaveRemove
1
2
3
4
5
  1. Platform Credentials — the active nav item, in the Platform group of the sidebar.
  2. Encrypted banner — a standing reminder that every value is Fernet-encrypted at rest and never echoed back.
  3. Configured / Not set badge — per-provider status. “Configured” means a stored row exists; the API never reveals the values.
  4. Provider fields — the credential inputs for that provider. Secret fields render as masked password inputs.
  5. Save / Remove — Save encrypts and upserts the row; Remove deletes it (and is disabled when nothing is stored).
The Platform Credentials view: one card per shared provider, secrets masked, status by badge.

25.2.1 What each provider expects

The field list per provider is authoritative on the server — the API validates a write against it (all required fields present and non-empty; unknown fields rejected) and advertises the field names only. The four core channels you will configure most often:

Table 25.1 — Core shared-credential providers and their required fields
ProviderUsed forRequired fieldsOptional
smsOutbound & 2-way SMSaccount_sid, auth_token, from_number
whatsappWhatsApp Business (Meta Cloud)token, phone_id, verify_tokenapp_secret
emailInbound/omni email (IMAP + SMTP)imap_host, user, pass, smtp_hostimap_port, smtp_port, from
aiThe shared LLM / AI keyapi_keymodel
More providers than channels

The card grid also surfaces transactional email (resend), speech-to-text / text-to-speech (stt / tts), a CRM connector (crm), and the social providers (facebook, instagram, twitter). The two WhatsApp/social app_secret / verify_token fields verify inbound webhook signatures — without them, inbound webhooks fail closed. Configure them the same way as the four above.

CloudCX · Administrator & Training ManualCh. 25 · Credentials vault
CloudCXCloudCX Administrator & Training Manual
Ch. 25 · Configuring credentials

25.3 Configuring and removing a credential

Saving is an upsert: a write validates and replaces the whole stored row for that provider. Blank fields are dropped, so you can save a partial card to keep existing values; only the recognised, non-empty fields are kept and encrypted.

  1. Open the provider card. Each card shows its Configured / Not set badge and the provider’s fields.
  2. Fill the required fields. Required fields must be present and non-empty; secret fields are masked. A field left blank keeps its current stored value (it is not cleared).
  3. Click Save. The console PUTs the card to /admin/platform/credentials/{provider}. The server validates, Fernet-encrypts the payload and upserts one row.
  4. Confirm the badge flips. On success you see “Saved. Stored encrypted on the server,” the badge becomes Configured ✓, and Remove is enabled.
  5. To retire a provider, click Remove. This deletes the stored row; the channel/AI degrades to disabled until you configure it again.
Worked example — configure the shared SMS sender

You want every reseller without its own SMS account to send through CloudCX’s shared number.

  1. Go to Platform › Platform Credentials and find the SMS card (badge: Not set).
  2. Enter the three required fields: Account SID = AC9f…, Auth token = the provider auth token (masked), From number = +6531591234.
  3. Click Save. The payload is encrypted and stored; the badge flips to Configured ✓.
  4. Verify the channel is enabled. Under Platform › Channels, confirm SMS is on (channels default enabled). The shared SMS sender is now live for any tenant not using its own.

Expected result: the SMS card reads Configured ✓; the From number is never displayed again; a test SMS from a non-BYO tenant is delivered from +6531591234.

25.3.1 When Save fails

Two failures are by design and worth recognising on sight:

422 — field problem

A required field is empty, or you sent an unexpected field. The card shows exactly which field(s); fix and re-save.

503 — encryption not configured

BYOND_CREDS_KEY is unset/invalid on the server. The save is refused; nothing is stored in plaintext. Set the key, then retry.

Removing a provider takes a channel offline

Remove is idempotent and immediate. Once the row is gone, that channel’s send (or the AI client, or the email poller) degrades to disabled for every tenant relying on the shared credential — resellers who brought their own are unaffected. Remove only when you intend the shared channel to go idle.

Shared vs. bring-your-own

These are the fallback credentials. A reseller you have granted bring-your-own (see the reseller entitlements chapter) overrides the shared key per channel. The shared vault is what every other reseller’s tenants use, so keep it current.

CloudCX · Administrator & Training ManualCh. 25 · Configuring credentials
CloudCXCloudCX Administrator & Training Manual
Ch. 25 · Password policy

25.4 Password policy

Open GovernanceSecurity. The Security view holds four live controls; the first is the password policy — a single, platform-wide rule for the minimum length and character classes a new password must contain. Defaults are deliberately permissive (minimum 8, no class requirements) so that turning the panel on changes nothing until you tighten it.

admin.cloudcx.app
Governance
Security
Licensing
Settings
ALL SYSTEMS OPERATIONAL
v4.8 · ap-southeast
Security// identity · policy · fraud defense
PRODAO
Trust & security

Security

Password policy

// live
Minimum length12
Require uppercase
At least one A–Z character.
Require lowercase
At least one a–z character.
Require digit
At least one 0–9 character.
Require symbol
At least one non-alphanumeric character.
Save policyReload
1
2
3
4
  1. Security nav item, in the Governance group.
  2. Minimum length — the floor for new passwords, bounded 1–128.
  3. Character-class toggles — require an uppercase, lowercase, digit and/or symbol.
  4. Save policy / Reload — persist the policy, or discard edits and re-read the stored values.
The password-policy panel: a minimum length plus four optional character-class requirements.

25.4.1 What the policy is — and is not

The policy is a partial update: each Save sends only the fields you changed, and omitted fields keep their stored value. Critically, it applies to new passwords only — it is checked at the user-creation and password-change call sites and never retro-invalidates existing users. Anything not a letter or digit counts as a “symbol”.

Minimum lengthmin_length
Integer, 1–128. The schema caps per-form password fields at 128, so a larger minimum would make every password impossible to set.
Require uppercaserequire_upper
At least one A–Z character. Default off.
Require lowercaserequire_lower
At least one a–z character. Default off.
Require digitrequire_digit
At least one 0–9 character. Default off.
Require symbolrequire_symbol
At least one non-alphanumeric character. Default off.
Worked example — enforce a 12-character mixed-class password
  1. Set Minimum length to 12.
  2. Turn on Require uppercase, lowercase and digit; leave Require symbol off.
  3. Click Save policy. The panel confirms; from now on a new password like summer2025 is rejected (no uppercase) while Summer2025XY is accepted.
  4. Note the rejection message. A violation returns a single human-readable 422: “Password does not meet the policy: must contain …” listing every unmet requirement.
Tightening the policy does not lock anyone out

Because existing passwords are never re-validated, raising the bar only affects the next password set on each account. Pair a stricter policy with a planned password-rotation campaign if you want everyone on the new standard.

CloudCX · Administrator & Training ManualCh. 25 · Password policy
CloudCXCloudCX Administrator & Training Manual
Ch. 25 · IP access rules

25.5 IP access rules

The IP access rules panel (also under Governance › Security) is a CIDR allow/deny firewall enforced in front of the whole API by middleware. Each rule is a network in CIDR notation and an action (allow or deny); a matching deny answers 403 before the request reaches any route.

admin.cloudcx.app
Governance
Security
Licensing
Settings
ALL SYSTEMS OPERATIONAL
v4.8 · ap-southeast
Security// IP access rules
PRODAO

IP access rules

// live
CIDRActionReason
203.0.113.0/24 AllowHQ office range
198.51.100.7/32 DenyAbusive host
CIDR e.g. 203.0.113.0/24 or 198.51.100.5/32 Deny Reason (optional) Add rule
1
2
3
4
5
  1. Security nav item — the IP-rules panel is on this page, below the policy and 2FA cards.
  2. Allow rule — an explicit allowlist entry (here, the HQ office range).
  3. Deny rule — an explicit block; 403 for any matching IP.
  4. CIDR / Action / Reason inputs — the new-rule row. The CIDR is normalised; a bare address becomes a /32 or /128.
  5. Add rule — creates the rule and invalidates the enforcement cache so it takes effect within seconds.
The IP access-rules panel: existing rules above, the add-rule row below.

25.5.1 Allowlist vs. blocklist — the matching logic

The behaviour depends entirely on whether any allow rule exists. This is the single most important thing to understand before you add one:

Adding your first allow rule can lock you out

The moment a single allow rule exists, every IP that does not match an allow rule is denied — including your own browser. Before adding any allow rule, add an allow rule that covers your current public IP (and your office/VPN range). Verify you are still in, then tighten. Deletion also invalidates the cache, so removing a bad rule recovers within the cache window.

Worked example — lock the admin API to two office ranges
  1. Confirm your public IP first. Note your current egress IP (for example 203.0.113.42).
  2. Add an allow rule for the HQ range — CIDR 203.0.113.0/24, action Allow, reason “HQ office”. Click Add rule. Because an allow rule now exists, you are in allowlist mode.
  3. Verify you still have access by refreshing the console — your IP is inside the range you just allowed.
  4. Add the second site198.51.100.0/24, Allow, “DR site”.
  5. Add a targeted deny for a known-bad host inside an allowed range — 203.0.113.99/32, Deny. On a prefix tie deny wins, and /32 is the most specific match, so that one host is blocked while the rest of 203.0.113.0/24 stays allowed.

Expected result: only the two office ranges reach the API; 203.0.113.99 is denied; every other IP gets 403 Access denied from your IP address.

How the client IP is resolved

The enforced IP is the first X-Forwarded-For hop, then X-Real-IP, then the socket peer — the same logic as the rate limiter. CloudCX Edge overwrites those headers at the public edge, so a client cannot spoof them. Rules are cached briefly (about 30 seconds); an add/delete invalidates the cache so changes apply on the next request.

CloudCX · Administrator & Training ManualCh. 25 · IP access rules
CloudCXCloudCX Administrator & Training Manual
Ch. 25 · Two-factor (MFA)

25.6 Two-factor authentication (TOTP)

The Two-factor authentication card on the Security page manages 2FA for your own signed-in account — it is self-service, opt-in, and off by default. CloudCX uses standard RFC 6238 TOTP (the six-digit codes from Google Authenticator, Authy, 1Password, etc.). The flow is enroll → activate → backup codes, and the seed is encrypted at rest like every other secret.

The 2FA enrollment lifecycle
Enroll
get secret + QR
Scan
authenticator app
Activate
enter 6-digit code
Backup codes
shown ONCE
admin.cloudcx.app
Two-factor authentication// your account
PRODAO

Two-factor authentication

Setup in progress
Open in app
Secret (manual entry)
JBSWY3DPEHPK3PXP
otpauth URI
otpauth://totp/CloudCX%20CaaS:ao@…
Enter the 6-digit code to activate
000000 Activate
1
2
3
  1. QR code — scan with any authenticator app. (The console renders a tappable otpauth:// deep link with no external library.)
  2. Secret / otpauth URI — for manual entry if you cannot scan. 2FA is not on yet at this stage.
  3. Activate — enter the current six-digit code; on success 2FA turns on and your backup codes are shown once.
The 2FA enrollment step: scan the QR (or enter the secret), then confirm with a six-digit code.

25.6.1 Turning 2FA on

  1. Click Enable 2FA. The console calls /auth/mfa/enroll, which generates a TOTP secret, stores it encrypted pending activation, and returns the secret + otpauth URI. 2FA is still off.
  2. Scan the QR code (or type the secret) into your authenticator app. A six-digit code starts ticking every 30 seconds.
  3. Enter the current code and click Activate. The server verifies it against the pending secret, sets mfa_enabled, and returns your one-time backup codes.
  4. Save the backup codes now. They are shown once and never again — only hashes are stored. Each is a single-use recovery code if you lose your authenticator.
Backup codes appear exactly once

Copy your backup codes to a password manager the instant they appear. There is no way to re-display them — if you lose both your authenticator and your codes, an administrator must reset your 2FA out-of-band. Enroll requires BYOND_CREDS_KEY (the seed is encrypted), so a 503 here means the key is unset.

25.6.2 Turning 2FA off

To disable, enter a current TOTP code or an unused backup code and click Disable 2FA. On success the secret and all backup codes are cleared. The status card then reads Not enabled with a fresh Enable 2FA button.

Try it — enrol your own admin account in 2FA
  1. On the Security page, click Enable 2FA and scan the QR with your phone’s authenticator.
  2. Type the current six-digit code and click Activate. Confirm the badge flips to Enabled ✓.
  3. Record the backup codes shown on screen, then sign out and back in — you should now be asked for a code (401 mfa_required drives the code prompt at login).
  4. Practise recovery: at the code prompt, use one of your backup codes instead of the app. It works once, then is spent.
Opt-in by design

2FA only ever adds a factor. A user who never enrols is unaffected, and /auth/token enforces a second factor only when that user has 2FA enabled. The Security page also shows an Email sign-in codes card for one-time login codes by email — the same opt-in philosophy.

CloudCX · Administrator & Training ManualCh. 25 · Two-factor (MFA)
CloudCXCloudCX Administrator & Training Manual
Ch. 25 · SSO / OIDC

25.7 Single sign-on (SSO / OIDC)

The last card on the Security page stores configuration for an external OpenID Connect identity provider (Okta, Entra ID, Google, Auth0, or a generic OIDC IdP). It records three things — the issuer URL, a client ID and a write-only client secret — behind an Enable SSO toggle.

admin.cloudcx.app
Single sign-on// SSO / OIDC
PRODAO

Single sign-on (SSO / OIDC)

// live
Enable SSO
Use an external OIDC identity provider for sign-in.
Issuer URLhttps://idp.example.com
Client IDyour-app-client-id
Client secretleave blank to keep current
Full SSO sign-in is coming — this saves the configuration; the interactive sign-in flow isn’t live yet. The client secret is write-only and is only sent when you enter a new value.
Save SSO configReload
1
2
3
4
  1. Enable SSO — the master toggle. Storing config does not change /auth/token; the login flow is a documented stub today.
  2. Issuer URL — your IdP’s OIDC issuer (discovery base).
  3. Client ID — the application/client identifier registered at the IdP.
  4. Client secret — write-only. Leave blank to keep the current value; send an empty value to clear it. Never returned.
The SSO/OIDC configuration card: issuer, client ID and a write-only client secret.
  1. Register CloudCX as an application at your IdP and collect the issuer URL, client ID and client secret.
  2. Enter the issuer and client ID in the panel.
  3. Paste the client secret. It is encrypted (Fernet) on save and never echoed; the card only shows whether a secret is configured. (A secret write needs BYOND_CREDS_KEY, else 503.)
  4. Toggle Enable SSO and click Save SSO config. The configuration is persisted.
The sign-in flow is an honest stub

This panel stores configuration only. The interactive OIDC sign-in (redirect to the IdP, callback, token exchange, ID-token validation, claim-to-user mapping) is not yet implemented — the /sso/login and /sso/callback endpoints deliberately return 501 Not Implemented. Turning on Enable SSO does not change /auth/token and does not create an SSO session. Keep your password / 2FA login working until the flow ships.

CloudCX · Administrator & Training ManualCh. 25 · SSO / OIDC
CloudCXCloudCX Administrator & Training Manual
Ch. 25 · Licensing

25.8 Licensing

Open GovernanceLicensing. This view shows the licensed seat and customer pool the platform has allocated to its resellers, and how much each reseller has consumed. Every number is derived live from real reseller entitlements — the console never fabricates a pool.

admin.cloudcx.app
Governance
Security
Licensing
Settings
ALL SYSTEMS OPERATIONAL
v4.8 · ap-southeast
Licensing// entitlements · platform pool
PRODAO
Entitlements

Licensing

Refresh
Resellers
12
partners
Licensed agent seats
2,400
allocated
Licensed customers
320
tenant cap
Tenants provisioned
86
live

Licensed seats by reseller

// allocated agent seats per partner
ResellerSeat capCustomer cap
ACAcme Comms500 licensed40
NVNova VoiceUnlimited0 = unlimited
1
2
3
4
  1. Licensing nav item, in the Governance group.
  2. Pool KPIs — total resellers, licensed agent seats, licensed customers and tenants provisioned, summed across partners.
  3. Seats by reseller — each partner’s licensed seat cap (where caps are actually stored).
  4. Unlimited (0 = unlimited) — an allotment of 0 means “no cap configured,” an honest reflection of the data, not a fake limit.
The Licensing view: the platform’s licensed pool and each reseller’s share.

25.8.1 How entitlements work

Every gated feature has a per-count entitlement: an allotment (the licensed cap) plus live usage. An action is blocked when granting it would push usage past the allotment. The catalogue covers seats and customers but also channels, bots, transcripts, QA, reports, MFA, SSO and more. Two conventions matter:

Where you edit caps

The Licensing view is read-only — it reports the pool. You change a reseller’s seat / customer caps and channel entitlements from Tenancy › Resellers (the entitlements drawer). See the reseller-entitlements chapter for the full procedure.

Per-feature usage pools

AI-session, transcript-minute, recording-storage and dialer-port pools are metered at the service layer and are not yet exposed through a platform metering API, so the “Per-feature usage pools” panel notes they will appear with live consumption once that endpoint ships — rather than showing a fabricated number.

CloudCX · Administrator & Training ManualCh. 25 · Licensing
CloudCXCloudCX Administrator & Training Manual
Ch. 25 · Platform settings

25.9 Global platform settings

Open GovernanceSettings. Alongside your signed-in identity and a “where to manage what” map, the Settings view holds the singleton Platform defaults — the platform-wide values new tenants inherit, plus the global maintenance switch.

admin.cloudcx.app
Settings// global config · defaults
PRODAO

Platform defaults

// live
Default timezoneAsia/Singapore
Default regione.g. ap-southeast
Recording retention (days)90
Recording on by default
New tenants record calls unless overridden.
Maintenance mode
Show a maintenance banner / restrict access platform-wide.
Maintenance messageShown to users while maintenance mode is on
Save settingsReload
1
2
3
4
  1. Default timezone — the platform-wide timezone (e.g. Asia/Singapore); defaults to UTC.
  2. Recording retention (days) — how long recordings are kept (must be ≥ 1; default 90).
  3. Recording on by default — new tenants record calls unless they override it.
  4. Maintenance mode + message — show a platform-wide maintenance banner / restrict access, with a custom message.
The Platform defaults panel: timezone, region, recording, retention and the maintenance switch.

Settings is a singleton with a partial-update save — only the fields you change are written; omitted fields keep their stored value, and a GET returns sensible defaults before any save. The fields:

Default timezonedefault_timezone
IANA timezone string; defaults to UTC. New tenants inherit it.
Default regiondefault_region
A region label such as ap-southeast. Optional.
Recording defaultrecording_default
Whether new tenants record calls by default. Default off.
Retention daysretention_days
Recording retention window; must be ≥ 1. Default 90.
Maintenance modemaintenance_mode
Platform-wide maintenance banner / access restriction. Default off.
Maintenance messagemaintenance_message
The text shown to users while maintenance mode is on.
Maintenance mode is platform-wide

Turning on maintenance mode affects every tenant, not one. Set the maintenance message first so users see something meaningful, schedule a window, and turn it off promptly when the work is done.

25.10 Chapter recap

With trust and global configuration under control, the platform is both connected to the outside world and defended against it. The next chapters move into day-two operations — monitoring, audit and the routines that keep all of this healthy.

CloudCX · Administrator & Training ManualCh. 25 · Security & administration
CloudCXCloudCX Administrator & Training Manual
Ch. 26 · Operations & Runbooks
Chapter 26

Operations, health, troubleshooting and runbooks

Everything in the previous chapters tells you how to configure CloudCX. This chapter tells you how to run it — calmly, on a Tuesday afternoon, and equally calmly at three in the morning. You will learn the two probes the platform exposes (/api/v1/health and /api/v1/ready), how to read the dashboard’s Service health panel honestly rather than optimistically, how the automated deploy verifies itself, and a set of step-by-step runbooks for the incidents you are most likely to meet: a degraded readiness check, a stuck deploy, CloudCX Cache pressure, CloudCX Switch dropping calls, and a flood of 429s. We close with a worked incident, a Try-it drill, and an FAQ.

Who this chapter is for

You do not need shell access to most of it — the dashboard and the two probe URLs cover day-to-day monitoring. The runbooks that touch the host (Docker, logs, migrations) assume an operator with SSH access to the CloudCX box and membership of the deploy group. Where a step is host-only it is marked HOST.

26.1 The operating model in one picture

CloudCX runs as a small set of containers on a single hardened host, fronted by CloudCX Edge (egress-only — there are no inbound public ports). The API is a CloudCX Core process; it talks to CloudCX DB for durable state and CloudCX Cache for ephemeral state (sessions, rate-limit counters, presence, short-lived tokens). Voice rides a separate plane: a CloudCX SBC SBC at the edge and CloudCX Switch for media, reached over the CloudCX Switch control socket. Knowing which box owns a symptom is half of every diagnosis.

The runtime planes you operate
EdgeCloudCX Edge — an egress-only secure tunnel with TLS, security headers and forwarding. No public inbound ports on the host.CloudCX Edge
ApplicationCloudCX Core API (CloudCX Core). Serves /api/v1, the admin console, web-chat sockets, the voice control socket server, and the background sweeps.api :8000
StateCloudCX DB = durable system of record. CloudCX Cache = sessions, rate-limit windows, presence, ephemeral tokens.byonddb · byondcache
Voice planeCloudCX SBC SBC (registrar / dispatcher) at the edge; CloudCX Switch for media + IVR, reached over the CloudCX Switch control socket (control socket).CloudCX SBC · CloudCX Switch
Triage mantra

Liveness, then readiness, then dependencies, then the plane that owns the symptom. Voice problems live on the voice plane; login/session problems live in CloudCX Cache; data problems live in CloudCX DB. Do not restart the API to fix a CloudCX DB outage — you will just restart a healthy process.

CloudCX · Administrator & Training ManualCh. 26 — Operations & Runbooks
CloudCXCloudCX Administrator & Training Manual
Ch. 26 · Operations & Runbooks

26.2 The two probes: /health and /ready

CloudCX deliberately exposes two probes, not one, because “the process is up” and “the process can do useful work” are different questions. Both are unauthenticated GETs under the API prefix, and both return the same compact JSON shape.

26.2.1 Liveness — GET /api/v1/health

Liveness answers one question: is the API process alive and serving HTTP? It touches no external dependency — no database, no CloudCX Cache. That is intentional: a liveness probe must not fail just because CloudCX DB is briefly slow, or an orchestrator would kill a process that was actually fine and make the outage worse. A 200 here means “the process answered”; nothing more, nothing less.

# Liveness — no dependencies touched
$ curl -s https://cloudcx.app/api/v1/health | jq
{
  "status": "ok",
  "service": "CloudCX CaaS API"
}

26.2.2 Readiness — GET /api/v1/ready

Readiness answers the harder question: can the API actually reach the things it needs? It runs one trivial probe per dependency — SELECT 1 against CloudCX DB and a PING against CloudCX Cache — and reports each one individually in a checks map. The overall status is "ok" only when every check is "ok"; if any check fails the overall status becomes "degraded" and the failing check carries an error label such as error: ConnectionError.

# Readiness — probes CloudCX DB + CloudCX Cache individually
$ curl -s https://cloudcx.app/api/v1/ready | jq
{
  "status": "degraded",
  "service": "CloudCX CaaS API",
  "checks": {
    "byonddb": "ok",
    "byondcache": "error: ConnectionError"
  }
}
The readiness probe never throws

Each dependency check is wrapped so that a failing dependency produces "error: <ExceptionClass>" in the map — the probe still returns HTTP 200 with a body of status: "degraded". So do not alert on the HTTP status of /ready alone; alert on the JSON status field and on individual checks. A monitor that only watches for non-200 will sleep straight through a CloudCX Cache outage.

Table 26.1 — The two probes at a glance
ProbeQuestion it answersTouchesUse it for
/api/v1/healthIs the process alive?NothingLiveness / restart decisions; the deploy gate
/api/v1/readyCan it reach its dependencies?CloudCX DB, CloudCX CacheTraffic / load-balancer admission; dependency alerting

26.2.3 Reading a probe by hand

  1. Open the URL in a browser — either probe renders as readable JSON. For liveness, visit cloudcx.app/api/v1/health; for readiness, cloudcx.app/api/v1/ready.
  2. For scripting, pipe through jq as shown above so the body is pretty-printed and you can assert on a field (.status or .checks.byondcache).
  3. Read the right field. On /ready, trust the JSON status and the per-dependency checks — not the HTTP code. On /health, the HTTP code is the signal (a non-200 means the process is not serving).
  4. Localise the fault. If exactly one check is in error, you have isolated the failing dependency without touching the host — jump to the matching runbook in § 26.5.
One bookmark, two answers

Bookmark both probes in your browser bar. Liveness for “is it up?” and readiness for “is it healthy?” — the pair answers the first two triage questions in under five seconds, before you ever reach for SSH.

CloudCX · Administrator & Training ManualCh. 26 — Operations & Runbooks
CloudCXCloudCX Administrator & Training Manual
Ch. 26 · Operations & Runbooks

26.3 Reading the dashboard honestly

When you open the admin console, the home view is your operational glance. Two things on it are about health: the Service health panel (which polls /api/v1/health on a timer) and the sidebar footer badge ( ALL SYSTEMS OPERATIONAL) with the build/region line. Read them together — and read them sceptically.

admin.cloudcx.app
Operate
Dashboard
Live Ops3
Platform
Call Routing
Channels6
CredentialsKEYS
ALL SYSTEMS OPERATIONAL
v4.8.2 · eu-west · build 2026.06
Dashboard// 01 — operations overview
PRODAO
Calls today
1,284
live now · 7
Open chats
42
waiting · 3
Agents online
58
of 64
P95 latency
86ms
api · /v1

Service health

// /api/v1/health
ServiceStateDetail
APAPI process okliveness 200 · uptime 6d
DBCloudCX DB okSELECT 1 · 4 ms
CACloudCX Cache degradederror: ConnectionError
FSCloudCX Switch (control socket) okreconnect armed
1
2
3
4
  1. Sidebar health badge — an at-a-glance roll-up plus the build line (v4.8.2 · eu-west · build 2026.06). Note the version + region before you raise a ticket.
  2. Environment pill PROD. Always confirm you are on the right environment before acting.
  3. Service health panel — polls /api/v1/health and decomposes readiness into one row per service, with the same ok / degraded states the probe returns.
  4. The honest signal — CloudCX Cache shows degraded with error: ConnectionError, exactly the per-check label from /ready. This is the row you act on.
The Dashboard with the Service health panel showing one degraded dependency.
Why the badge can lie

The sidebar badge is a summary; it is fed by liveness, which is green whenever the process answers. The process can be perfectly alive while CloudCX Cache is down. So the badge being green does not mean the platform is healthy — the Service health panel (and /ready) are the authoritative source. Treat a green badge with a degraded row as “degraded”, not “operational”.

CloudCX · Administrator & Training ManualCh. 26 — Operations & Runbooks
CloudCXCloudCX Administrator & Training Manual
Ch. 26 · Operations & Runbooks

26.4 Deploys verify themselves

CloudCX ships by git push to main. A webhook listener on the host validates the GitHub HMAC signature, checks the branch, and fires the deploy script. The script is single-flight (it takes a lock so two deploys can never overlap), ERR-trapped, and — the part that matters for operations — it health-gates itself: it polls liveness for up to two minutes and refuses to declare success until the new container answers 200.

The self-verifying deploy pipeline
pushto main
webhookHMAC + branch
buildcompose build
migratealembic upgrade
upcompose up -d
health-gate40 × 3s poll
notifyTelegram

The health gate is literally a loop that curls liveness on the local port and breaks as soon as it sees 200; if it never does, the deploy is marked FAILED at stage=health_check and a Telegram alert fires with the last lines of the deploy log. Success is only announced when HEAD actually changed, so a no-op redeploy stays quiet.

# the gate, paraphrased from deploy/auto_deploy.sh
STAGE="health_check"; HEALTH="000"
for _ in $(seq 1 40); do
  sleep 3
  HEALTH=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8200/api/v1/health)
  [ "$HEALTH" = "200" ] && break
done
[ "$HEALTH" = "200" ] || { # FAILED -> notify; false; }

26.4.1 What to watch during a deploy

  1. Watch the channel, not the clock. The deploy notifier posts to Telegram on every failure and on every successful change. No message after a push to main usually means the webhook never fired — check the listener, not the app.
  2. Confirm with liveness. After a success message, hit /api/v1/health yourself. The gate already did this, but a second pair of eyes costs nothing.
  3. Confirm dependencies with readiness. The gate checks liveness only. After a migration, also hit /api/v1/ready — a bad migration can leave the process alive but the database checks unhappy.
  4. If it says FAILED at migrate, do not retry blindly — jump to the migration runbook (§ 26.5.4). A half-applied schema must be understood before it is re-run.
Migrations run before traffic

The pipeline runs alembic upgrade head before compose up -d brings the new container into service, so the schema is current before the new code serves a single request. This ordering is why a failed migration fails the whole deploy rather than silently shipping code that expects a column that does not exist yet.

26.4.2 The fail-safe that protects you from yourself

CloudCX refuses to start at all in a production-like environment if it still carries shipped development secrets — the dev JWT secret, the default CloudCX Switch control socket password, or the changeme database password. The process raises a clear error and exits, which means the deploy gate never sees a 200 and the deploy is correctly marked failed. This is a feature: a misconfigured production box fails loudly at boot instead of running with forgeable tokens.

If a fresh prod deploy never goes green

A brand-new production container that never reaches 200 — with the log showing “Refusing to start in production with insecure development defaults” — is not a crash. It is the config guard. Set strong JWT_SECRET, FS_ESL_PASSWORD and a real database password in the environment, then redeploy. Never “fix” this by flipping ENVIRONMENT away from production.

CloudCX · Administrator & Training ManualCh. 26 — Operations & Runbooks
CloudCXCloudCX Administrator & Training Manual
Ch. 26 · Operations & Runbooks

26.5 Runbooks

Each runbook below is a fixed sequence: symptom → confirm → act → verify. Always finish on a verify step — an incident is not closed until a probe agrees with you. Host-only steps are marked HOST.

26.5.1 Runbook A — readiness is degraded

Symptom. The Service health panel shows a red/amber row, or /ready returns status: "degraded" with one check in error.

  1. Confirm and localise. Read /api/v1/ready. Note which key is in error (byonddb or byondcache) and the exception class after error:.
  2. Check liveness is still green. Hit /api/v1/health. If liveness is also failing, the process itself is down — go to Runbook B instead.
  3. If byonddb is the failing check HOST — verify the database container is up and not out of connections or disk: docker compose ps byonddb and docker compose logs --tail=100 byonddb. A full disk or exhausted connection pool is the usual cause.
  4. If byondcache is the failing check — go to Runbook C (CloudCX Cache pressure). Logins and rate-limiting degrade first; the rate limiter fails open, so traffic still flows.
  5. Verify. Re-read /api/v1/ready until status returns to "ok" and every check reads "ok". Only then mark the incident resolved.

26.5.2 Runbook B — liveness is down (API not serving)

Symptom. /api/v1/health times out or returns a non-200; the console fails to load.

  1. Confirm from two vantage points. Try the public URL and the local port on the host (curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8200/api/v1/health). If local is 200 but public is not, the fault is the edge (tunnel/CloudCX Edge), not the API.
  2. Inspect the container HOSTdocker compose ps api and docker compose logs --tail=200 api. Look for a startup exception, an OOM kill, or the config-guard message from § 26.4.2.
  3. Check memory. The host has no swap; an OOM will be in dmesg. If the API was OOM-killed, do not just restart it — find what grew (see Runbook C) before it happens again.
  4. Restart only the API HOST if the logs show a clean but stuck process: docker compose up -d api. The background sweeps and the voice control socket server re-arm themselves on boot.
  5. Verify. Watch liveness flip to 200, then read /api/v1/ready to confirm dependencies reattached.
Never take strikerpulse down

The CloudCX stack shares its host with another product. Every container has a mem_limit and binds to loopback for exactly this reason. When you act on the host, stay inside the CloudCX compose project — never touch the host’s own CloudCX Edge/byondcore services or ports 443/22/8100–8104.

CloudCX · Administrator & Training ManualCh. 26 — Operations & Runbooks
CloudCXCloudCX Administrator & Training Manual
Ch. 26 · Operations & Runbooks

26.5.3 Runbook C — CloudCX Cache pressure (sessions / rate-limits / presence)

Symptom. The byondcache check is in error, or users are being logged out, or presence/live-ops looks stale. CloudCX Cache is capped (maxmemory 200mb, eviction policy allkeys-lru), so under pressure it evicts the least-recently-used keys rather than refusing writes.

  1. Confirm the blast radius. CloudCX Cache holds ephemeral state only — sessions, rate-limit windows, presence, short-lived provisioning tokens. Durable data is safe in CloudCX DB. So “CloudCX Cache is unhappy” means “people re-authenticate and counters reset”, not “data is lost”.
  2. Check memory + evictions HOSTdocker compose exec byondcache byondcache-cli INFO memory and … INFO stats | grep evicted_keys. Rising evicted_keys with used_memory pinned at the cap confirms LRU pressure.
  3. Reassure on rate limiting. The limiter fails open: if CloudCX Cache is unreachable, requests are allowed, not blocked. So a CloudCX Cache outage relaxes throttling — it never causes a 429 storm (that is Runbook D).
  4. Recover. If CloudCX Cache is down, restart it HOST (docker compose up -d byondcache); sessions are rebuilt as users sign in again. If it is merely under sustained pressure, that is a capacity signal — raise maxmemory deliberately, do not just keep restarting.
  5. Verify. /api/v1/ready shows byondcache: ok; a test login succeeds and the session persists across a page reload.

26.5.4 Runbook D — a flood of 429 Too Many Requests

Symptom. Clients (often an integration calling the programmable API) report 429s with a Retry-After header.

  1. This is the limiter working, not a fault. CloudCX applies fixed-window limits keyed by route + client IP — a strict tier on POST /auth/token and on public webhooks, a generous global default elsewhere. A 429 means a caller crossed its window.
  2. Identify the caller. The limiter keys on the real client IP from the proxy headers (X-Forwarded-For first hop, then X-Real-IP), which CloudCX Edge overwrites at the edge so they cannot be spoofed. Find the offending IP/integration in the logs.
  3. Decide: client or platform. A single misbehaving integration hammering /auth/token should fix its own retry/back-off. A broad, legitimate increase in traffic is a signal to raise the relevant limit deliberately via configuration.
  4. Honour Retry-After. Tell the integration owner the window resets after the seconds in that header; well-behaved clients simply wait and succeed.
  5. Verify. After the window or a deliberate limit change, the caller’s requests return 200 again.

26.5.5 Runbook E — voice problems (CloudCX Switch / control socket)

Symptom. Calls fail or drop, agents cannot be reached, IVR does not answer — while the API and database are perfectly healthy.

  1. Confirm the plane. Voice rides its own plane. If /api/v1/ready is fully ok but calls fail, the fault is on the voice plane (CloudCX SBC / CloudCX Switch), not the app. Do not restart the API.
  2. Know that the API self-heals its control socket link. The telephony service and the inbound voice socket are started in guarded blocks: if CloudCX Switch is unreachable at boot, the API still starts and reconnects when CloudCX Switch returns. So a voice outage never blocks deploys or logins.
  3. Check CloudCX Switch HOSTdocker compose ps CloudCX Switch and its logs. control socket listens on loopback (:8021); the public SIP arrives from the CloudCX SBC edge.
  4. Walk the path. Edge (CloudCX SBC registrar) → media (CloudCX Switch) → the API’s control socket outbound server. A registration problem is usually the edge; a media/IVR problem is usually CloudCX Switch.
  5. Verify. Place a test call (or watch Live Ops). Confirm registration, audio, and that the API’s control socket connection re-armed in the logs.
Read the JSON logs by event name

In production the API logs structured JSON. Operationally useful event names include api.telephony_start_failed, api.voice_socket_start_failed, api.dialer_resume_failed, the billing sweep failures (api.rating_sweep_failed, api.dunning_sweep_failed), and api.insecure_jwt_secret. Grepping the log for the event name is faster than scrolling.

CloudCX · Administrator & Training ManualCh. 26 — Operations & Runbooks
CloudCXCloudCX Administrator & Training Manual
Ch. 26 · Operations & Runbooks

26.5.6 Runbook F — a deploy is stuck or failed

Symptom. A Telegram message reads CLOUDCX DEPLOY FAILED, or no message arrives at all after a push to main.

  1. Read the failing stage. The alert names the stage (git_fetch, build, migrate, up, or health_check) and includes the last lines of the deploy log. The stage tells you which runbook applies.
  2. No message at all? The webhook never fired or the listener is down — check the listener service on the host, not the API. The listener validates the GitHub HMAC and gates on branch main.
  3. Failed at health_check? The new container built and migrated but never answered 200 within two minutes. Read the API logs (Runbook B) — a config-guard refusal (§ 26.4.2) is the classic cause on a fresh prod box.
  4. Failed at migrate? A migration errored before the new code went live, so old code is still serving. Inspect the migration, fix forward, and let the next push re-run alembic upgrade head. Never hand-edit the schema to “match” the code.
  5. Verify. A clean deploy ends with CLOUDCX DEPLOY OK and a green liveness probe; confirm both, then read /ready.

26.6 Worked example — the 09:14 CloudCX Cache blip

A worked incident, start to finish, using only the tools in this chapter.

09:14 — Alert

Two agents report being logged out mid-shift. The sidebar badge still reads ALL SYSTEMS OPERATIONAL — tempting to dismiss it.

09:15 — Confirm

You open /api/v1/ready. status: "degraded", byonddb: ok, byondcache: error: ConnectionError. The Service health panel agrees. The badge was lying.

09:16 — Scope

CloudCX Cache is ephemeral (Runbook C). Sessions and rate-limit windows are affected; no durable data is at risk. The rate limiter is failing open, so traffic still flows.

09:18 — Act & verify

On the host: INFO memory shows used_memory pinned at the 200 MB cap with evicted_keys climbing. You restart CloudCX Cache; /ready returns to all-ok; a test login persists across a reload. Incident closed; capacity flagged for review.

# 09:15 — confirm and localise
$ curl -s https://cloudcx.app/api/v1/ready | jq '.status, .checks'
"degraded"
{ "byonddb": "ok", "byondcache": "error: ConnectionError" }

# 09:18 — recover + verify (HOST)
$ docker compose exec byondcache byondcache-cli INFO stats | grep evicted_keys
evicted_keys:184213
$ docker compose up -d byondcache
$ curl -s https://cloudcx.app/api/v1/ready | jq '.status'
"ok"

The lesson. The badge said green; readiness said degraded; readiness was right. The whole incident was diagnosed from a browser before anyone touched the host, and the fix was scoped because we knew CloudCX Cache holds ephemeral state. Confirm, scope, act, verify — in that order.

CloudCX · Administrator & Training ManualCh. 26 — Operations & Runbooks
CloudCXCloudCX Administrator & Training Manual
Ch. 26 · Operations & Runbooks

26.7 Day-two checklist

A short routine catches most trouble before a user does. None of it needs the host.

Each morning

  • Liveness 200 at /api/v1/health.
  • Readiness all-ok at /api/v1/ready.
  • Service health panel: every row ok.
  • Build line matches the expected release (v4.8.2, region, build date).

After every deploy

  • CLOUDCX DEPLOY OK received with the right commit.
  • Liveness re-checked by hand.
  • Readiness re-checked (catches a bad migration).
  • A quick smoke: log in, open Live Ops, place a test call.
Liveness
cloudcx.app/api/v1/health
Readiness
cloudcx.app/api/v1/ready
Deploy alerts
Telegram (fail always · success on change)
Health-gate
40 × 3s liveness poll = ~2 min
CloudCX Cache cap
200 MB · allkeys-lru
Rate limiter
fixed-window · fails OPEN
Try it — run the morning health drill

Goal: read both probes correctly and prove you can tell “up” from “healthy”. (Use a non-production environment if you have one.)

  1. Read liveness. Open /api/v1/health. Write down the status and service. What did this probe not check?
  2. Read readiness. Open /api/v1/ready. Record the overall status and each entry in checks.
  3. Reconcile with the dashboard. Compare the Service health panel to the readiness JSON. Do the per-service states match the checks map? Does the sidebar badge agree — or is it summarising liveness only?
  4. Predict a failure. If CloudCX Cache went down right now, what would each probe say (HTTP code and JSON status), and would users see 429s? (Answers: liveness stays 200/ok; readiness stays HTTP 200 but JSON status: "degraded" with byondcache: error: …; no 429s — the limiter fails open.)

26.8 Frequently asked questions

Why are there two probes? health vs ready
Liveness (/health) means “the process is up” and touches nothing; readiness (/ready) means “it can reach CloudCX DB and CloudCX Cache”. Restart decisions watch liveness; traffic/dependency decisions watch readiness. Conflating them gets a healthy process killed during a dependency blip.
Should I alert on the HTTP status of /ready? no
No. Readiness returns HTTP 200 even when degraded — the failure is in the JSON. Alert on the body’s status field and on each entry in checks.
CloudCX Cache is down — will users get rate-limited to death? no
No. The limiter fails open: if CloudCX Cache is unreachable, requests are allowed. A CloudCX Cache outage relaxes throttling; it never causes a 429 storm.
Did a CloudCX Cache restart lose data? no durable data
CloudCX Cache carries ephemeral state only — sessions, rate-limit windows, presence, short-lived tokens. Durable records live in CloudCX DB. Users simply re-authenticate.
The deploy says FAILED at health_check on a new prod box. config guard
Almost always the production secret guard: the app refuses to start with the dev JWT secret, the default control socket password, or the changeme DB password. Set strong values and redeploy — do not weaken ENVIRONMENT.
Calls are failing but /ready is all green. voice plane
Readiness only checks CloudCX DB and CloudCX Cache. Voice rides CloudCX SBC + CloudCX Switch. A green /ready with failing calls points squarely at the voice plane — not the API. The API self-heals its control socket link on reconnect.
Can I just restart the API to fix things? rarely
Only when its own logs show a stuck-but-clean process. Restarting the API does nothing for a CloudCX DB outage, a CloudCX Cache cap, or a voice fault — and on a no-swap shared host, needless churn risks the neighbour. Localise first.
You can now operate the platform

You can read both probes, interpret the dashboard without being fooled by a summary badge, trust the self-verifying deploy, and walk a real incident through confirm → scope → act → verify. Keep the two probe URLs bookmarked — they answer the first two questions of every incident before you ever open a terminal.

CloudCX · Administrator & Training ManualCh. 26 — Operations & Runbooks
CloudCXCloudCX Administrator & Training Manual
Ch. 27 · Glossary & appendices
Chapter 27

Glossary and appendices

This final chapter is the manual’s quick-reference shelf — the page you keep open on a second monitor. It collects the things you look up rather than read: every keyboard shortcut in the console, a complete status and enum reference that decodes the coloured pills you see on tenants, invoices, calls and messages, the escalation paths and internal staff portals you reach when something is wrong, the support contacts and severity/response targets, and a master glossary of every term used across the preceding twenty-six chapters. Nothing here is new behaviour — it is the rest of the book, indexed for speed.

How to use this chapter

Treat §27.3 (status reference) as a lookup table: when a pill on a screen reads overdue or undelivered, find the object here to learn what the value means, what moves it, and what to do next. Each entry carries a cross-reference (e.g. § 23.4) back to the chapter that documents the full workflow. The values shown are the exact strings CloudCX stores in its data model, so they also match what you see in exports, the API and the support portal.

27.1 Conventions used across the appendices

A handful of conventions keep these tables compact and unambiguous:

Print this chapter on its own

From your browser’s print dialog you can usually print a page range. This chapter is designed to stand alone — pin the status reference (§27.3) and the escalation matrix (§27.4) by your desk as a one-page cheat-sheet for the operations team.

27.2 Keyboard shortcuts and navigation

The CloudCX admin console is mouse-driven, but a small set of global keys make day-to-day navigation faster. The most important is the universal search — one box at the top of every view that finds resellers, tenants, DIDs and IP addresses — reachable from anywhere with a single chord.

admin.cloudcx.app
Operate
Dashboard// 01
Reports// 02
Tenancy
Resellers12
Tenants86
ALL SYSTEMS OPERATIONAL
v4.8.2 · eu-west
Dashboard// platform overview · all resellers
ENV: PROD SA
Operate

Platform overview

Press ⌘K (Ctrl+K) anywhere to jump to search · Esc closes panels.

Universal search

focused
QueryacmeMatches reseller & tenant names, DID numbers and IP / CIDR rules.
Acme Comms · reseller Acme Retail · tenant +65 6xxx · DID
1
2
3
4
  1. Navigation rail — the grouped left menu (Operate · Tenancy · Revenue · Platform · Voice · Governance). Click a group item to switch views; the active item carries the gradient tab.
  2. Universal search box — focused here (pink ring). Type to match across resellers, tenants, DIDs and IP rules without leaving the current page.
  3. ⌘K hint chip — the shortcut badge. Pressing +K (macOS) or Ctrl+K (Windows/Linux) focuses this box from anywhere.
  4. Live results — matches appear inline, tagged by type, so you can recognise the right entity before opening it.
The console topbar with universal search focused via the ⌘K shortcut.
ShortcutActionWhere it works
⌘K / Ctrl+KFocus the universal search box in the topbar.Anywhere in the admin console.
EscClose the open drawer, modal or the mobile navigation overlay.Admin console (any open panel).
EnterSubmit the focused form / confirm the primary action of a dialog.Forms and modals, console-wide.
Tab / Shift+TabMove forward / backward through form fields (standard browser focus order).All forms.
Enter (dial field)Place a call from the softphone’s number field.Agent Desktop softphone (§17, §12).
Enter (composer)Send the chat / message; Shift+Enter inserts a newline.Agent inbox composer (§16, §17).
0–9 # *Send DTMF tones while a call is connected (dialpad keys).Agent softphone, in-call (§12).
Table 27.1 — Keyboard shortcuts across the CloudCX surfaces.
The console is deliberately keyboard-light

CloudCX intentionally avoids a large hidden hot-key surface so that browser and screen-reader shortcuts keep working predictably. The two you need are ⌘K/Ctrl+K to search and Esc to back out. Everything else is a normal, focus-ordered web form — Tab and Enter behave exactly as they do anywhere else.

CloudCX · Administrator & Training ManualCh. 27 — Glossary & appendices
CloudCXCloudCX Administrator & Training Manual
Ch. 27 · Glossary & appendices

27.3 Status and enum reference

Almost every object in CloudCX carries a status — a small controlled value that drives the coloured pill on the screen, the available actions, and the way usage is rated and reported. This section is the authoritative decoder for those values. They are grouped by area; each table lists the stored value, its meaning, and where it sits in the object’s lifecycle.

27.3.1 Accounts, users and access

ObjectValueMeaning & lifecycle
User status
§4, §7
active activeThe account can sign in and is counted against licence seats.
inactive inactiveDisabled by an administrator; cannot sign in, frees the seat. Reversible.
locked lockedLocked after failed sign-ins or by an admin. Requires an unlock (§7.6) before sign-in resumes.
User type / role
§4.2
adminPlatform administrator — full control plane access (this manual’s audience).
resellerWhite-label partner operator; scoped to their own tenants and book (§5, reseller manual).
tenantCustomer-side administrator for one tenant.
subtenantAdministrator of a tenant’s sub-organisation (departmental split).
tlTeam leader / supervisor — live monitoring and quality (§18, §19).
agentFront-line agent — the Agent Desktop only (§17).
IP rule action
§25
allow allowRequests from the matching CIDR are explicitly permitted.
deny denyRequests from the matching CIDR are blocked at the edge.
Agent presence
§17, §18
available availableReady · routing on. Eligible for new interactions from any queue the agent staffs.
on break breakPaused · no new work routed; existing interactions continue.
wrap-up acwAfter-call work — finishing notes/disposition before becoming available again.
offline offlineLogged out of the queue; not routable.
Table 27.2 — Account, role, access and presence values. (Live presence is held in the routing layer, not the user record.)
Status is not the same as presence

A user’s status (active/inactive/locked) is a persistent property of the account record — it governs whether they may sign in at all. An agent’s presence (available/break/acw/offline) is live, in-memory routing state that changes minute to minute. A perfectly active agent can still be offline, and the wallboard shows presence, not status.

27.3.2 Routing, queues and conversations

ObjectValueMeaning
Queue strategy
§10.3
longest_idleDefault. Offer to the available member who has been idle the longest (fairest workload spread).
round_robinCycle through eligible members in order.
fewest_callsOffer to the member who has handled the fewest interactions so far.
priorityOffer by the member’s configured priority/skill weighting first.
Conversation status
§13, §16
open openA live thread on a digital channel, not yet claimed by an agent. Lifecycle: open → assigned → closed.
assigned assignedClaimed by / routed to a specific agent who is handling it.
closed closedResolved and archived; reopening starts a fresh thread.
Message direction
§14, §16
inboundA turn from the visitor / customer.
outboundA turn from the agent or the system.
Channel
§12–§16
voice · webchat · whatsapp · sms · email · social · any
Table 27.3 — ACD distribution strategies, conversation lifecycle and channel discriminators.
CloudCX · Administrator & Training ManualCh. 27 — Glossary & appendices
CloudCXCloudCX Administrator & Training Manual
Ch. 27 · Glossary & appendices

27.3.3 Voice — calls, campaigns and numbers

ObjectValueMeaning
CDR direction
§12.5
inbound / outboundWhether the call arrived at or left the platform. billsec (billable seconds) and duration_sec distinguish answered time from total time.
hangup_causeThe SIP/CloudCX Switch release reason (e.g. NORMAL_CLEARING, USER_BUSY, NO_ANSWER, CALL_REJECTED). A zero billsec with NO_ANSWER is an unanswered call.
Campaign status
§11.2
draft draftBeing built; the dialer has not started. Lifecycle: draft → running → paused/completed.
running runningThe dialer is actively placing calls to pending contacts.
paused pausedTemporarily halted; resumable. completed when the list is exhausted.
Campaign contact
§11.3
pending pendingNot yet dialled.
dialing dialingA call is being placed to this contact right now.
connected connectedAnswered and bridged to an agent / flow.
no_answer no_answerRang with no answer; eligible for retry per policy.
busy busyEngaged tone; eligible for retry.
failed failedCould not be completed (bad number, congestion, blocked).
DID number
§8.1
available availableIn inventory, ready to be assigned to a tenant / flow.
local | tollfree | mobileThe number_type — drives presentation and, with the rate card, price.
DNC scope
§11.5
call · sms · whatsapp · bothWhat an entry on the Do-Not-Contact list suppresses for a number.
Table 27.4 — Voice call, outbound campaign and number values. Voice lifecycle fields are plain strings so engines and the builder can evolve them (§11).

27.3.4 Messaging — SMS lifecycle, sender IDs and surveys

The SMS delivery lifecycle is the most detailed status path in the platform, because a message’s fate is reported asynchronously by the carrier via a delivery receipt (DLR). Read it left to right:

SMS message delivery lifecycle (MT = outbound)
queuedaccepted by us
submittedhanded to carrier
sentleft the SMSC
deliveredDLR: success
undeliveredDLR: carrier reject
failedno carrier accept
receivedMO / inbound
ObjectValueMeaning
SMS message
§14.2
queued queuedAccepted by CloudCX, awaiting submission to a carrier route.
submitted submittedHanded off to the carrier / SMSC; submitted_at is stamped.
sent sentLeft the SMSC toward the handset; awaiting the final DLR.
delivered deliveredTerminal success. The DLR confirms handset receipt; delivered_at is stamped.
undelivered undeliveredTerminal. Carrier accepted but could not deliver; see error_code / error_detail.
failed failedTerminal. No carrier accepted it (no route, blocked, bad number).
received receivedAn inbound (MO) message landed on one of your numbers.
SMS encoding
§14.2
GSM7Standard 7-bit alphabet; 160 chars per segment.
UCS2Unicode (emoji / non-Latin); 70 chars per segment. Drives segments and therefore price.
Sender ID
§14.3
pending pendingRegistration submitted, awaiting carrier/regulatory approval. Lifecycle: pending → approved | rejected.
approved approvedCleared for use; only an approved sender may be presented on the send path.
rejected rejectedDeclined; see notes. Kinds: alphanumeric · longcode · shortcode.
SMS campaign
§14.4
draft draftBeing composed. Lifecycle: draft → scheduled → sending → completed (or cancelled).
scheduled scheduledQueued to start at scheduled_at.
sending sendingThe runner is paging through recipients now.
completed completedAll recipients processed; sent/delivered/failed counters final.
cancelled cancelledStopped before completion by an operator.
Survey invite
§20
pending pendingIssued, not yet answered. Kinds: csat · nps · custom.
completed completedRespondent submitted; the score is recorded.
Table 27.5 — SMS message, encoding, sender-ID, campaign and survey-invite values.
“sent” is not “delivered”

A common support confusion: a message stuck at sent has left the carrier but no positive DLR has arrived. That is normal for minutes, and some carriers never return a DLR at all. Only delivered is a confirmed handset receipt; undelivered/failed carry the reason in error_code. Do not bill or refund on sent alone — wait for the terminal state.

CloudCX · Administrator & Training ManualCh. 27 — Glossary & appendices
CloudCXCloudCX Administrator & Training Manual
Ch. 27 · Glossary & appendices

27.3.5 Billing — accounts, charges, invoices and payments

ObjectValueMeaning
Billing account
§23.1
active activeNormal trading. mode is postpaid (invoice then collect) or prepaid (collect then spend).
suspended suspendedService blocked — usually by dunning for non-payment, or manually. Reversible on payment (§24.4).
closed closedAccount terminated; no further charges.
Journal entry
§23.1
chargePriced usage — positive, increases what the party owes.
invoiceA grouping document — does not move the balance.
paymentMoney received — negative, decreases what they owe.
creditA goodwill / promotional credit — negative.
adjustmentA manual correction — signed either way.
refundMoney returned — negative.
Charge status
§23.3
pending pendingRated but not yet on an invoice. Lifecycle: pending → invoiced (or void).
invoiced invoicedPulled into a numbered invoice; will not be re-grouped.
void voidCancelled before invoicing; excluded from totals.
Invoice status
§23.4, §24.4
draft draftBuilt but not issued; still editable.
open openIssued and awaiting payment.
partial partialPart paid — amount_paid is below the total.
paid paidTerminal. Settled in full; paid_at stamped.
overdue overduePast its due date and unpaid — the dunning workflow acts on this (§24.4).
void voidCancelled; its charges return to pending or are written off.
Payment status
§23.5
pending pendingInitiated online, awaiting the gateway result.
succeeded succeededConfirmed (the credit is posted only on the verified Stripe webhook).
failed failedDeclined / errored; no credit posted.
refunded refundedReversed back to the payer.
Number charge
§23.2
active activeA rented number billed monthly.
released releasedNo longer charged from the release date.
Table 27.6 — Billing object values. Sign conventions: charges are positive; payments / credits / refunds are negative on the ledger.

27.3.6 Worked example — decoding a tenant’s screen at a glance

A reseller forwards a screenshot of one of their tenants and says “something looks wrong, can you check?” Using only this reference you can read the whole situation in seconds. Here is the row they sent:

admin.cloudcx.app#tenants

Tenant — Acme Retail

suspended
ObjectStateDetail
ARBilling account suspendedmode: postpaid
Latest invoice INV-2026-0414 overduedue 14 days ago
Outbound campaign Spring promo pausedauto-paused
Last SMS blast sending1,204 / 5,000
Sender ID ACME approvedalphanumeric
A tenant summary as forwarded — every pill is decodable from §27.3.

Reading it with the tables above:

  1. The account is suspended, the invoice is overdue. Table 27.6 tells you these two facts are causally linked: an overdue invoice triggers dunning, which suspends the account (§24.4). This is not a fault — it is the credit-control system working as designed.
  2. The campaign is paused, the blast is mid-sending. A suspended account blocks new chargeable work, so the dialer auto-paused; the in-flight blast is allowed to drain. Neither is broken.
  3. The sender ID is approved. Messaging capability itself is fine — this rules out a registration problem.
  4. Conclusion to send back: “Nothing is broken. The account is suspended for an invoice 14 days overdue. Record the payment (or post a credit) and the account reactivates; the campaign can then be resumed.” You diagnosed a billing state, not an outage — no escalation needed.
The reflex to build

Before escalating anything, decode every pill on the screen first. The vast majority of “it’s broken” reports are a perfectly healthy system in a state the reporter did not recognise — a suspended-for-non-payment account, a sent-not-yet-delivered message, or an offline agent. The status reference turns those into a one-line answer.

CloudCX · Administrator & Training ManualCh. 27 — Glossary & appendices
CloudCXCloudCX Administrator & Training Manual
Ch. 27 · Glossary & appendices

27.4 Escalation paths and the internal staff portals

When a status really does indicate trouble, CloudCX separates the work across two internal, staff-only surfaces that sit alongside the customer-facing console. Knowing which one to reach for — and the guardrails on each — is the difference between a five-minute fix and a self-inflicted outage.

Support portal — /support

Tier-1/2 support agents. Look up any customer, see their 360 (status, entitlements, balance, unpaid invoices, recent CDRs and threads, SIP registration), work tickets, and take bounded, audited, reason-required account actions (suspend/reactivate, password-reset email, toggle a feature, regenerate an invoice, record a manual payment/credit, requeue a stuck message). No impersonation, no raw secret reveal.

Ops / NOC portal — /ops

Operations / NOC engineers. Live platform and carrier health, a live severity-classified event stream (the “terminal”), an AI explain-only assistant for logs, and tightly-controlled engine restarts (CloudCX Switch, CloudCX SBC, CloudCX Media, SMPP) behind heavy guardrails. Reserved for the ops / admin staff roles.

Both portals have their own staff authentication (separate from customer and admin logins), three staff roles — support, ops and admin — and they audit every state-changing action (who, what, why, before/after, IP, timestamp) with a real-time alert to admins. PII is scrubbed in all staff views; secrets are never returned.

cloudcx.app/ops
NOC
Health
Event streamSSE
Carriers100+
Engine control
NOC ONLINE
role: ops
Live health// platform + carrier NOC
PRODOP
SBC · CloudCX SBC
UP
312 dialogs
CloudCX Switch
UP
88 channels
SMPP binds
19/20
1 unbound
API · DB · CloudCX Cache
OK
loops live

Event stream — severity-classified, PII-scrubbed

live
REDSMPP bind carrier-7 dropped — rebind attempted
YELLOWTrunk SG-LCR-2 ASR fell to 41% (last 5m)
NEUTRALRating loop completed — 14,208 events priced

Engine control — allow-listed operations

ops/admin only
CloudCX SBC: reload graceful CloudCX Switch: reloadxml CloudCX Switch: restart ⚠ 23 live calls will drop
1
2
3
4
  1. NOC navigation — Health, the live Event stream, per-carrier status, and Engine control. Engine control is only shown to the ops / admin staff roles.
  2. Health tiles — per-node status: the SBC (CloudCX SBC dialogs), CloudCX Switch channels, SMPP carrier binds (one unbound here), and the API/DB/CloudCX Cache core with its background loops.
  3. Severity-classified event stream — the live “terminal”, tagged RED / YELLOW / NEUTRAL and PII-scrubbed.
  4. Live-impact warning — a full restart shows how many live calls would drop, and requires a typed reason (≥10 chars) and confirmation. Graceful-first operations (reload/reloadxml) drop no calls.
The Ops / NOC portal: health, the severity-classified event stream, and guarded engine control.
Engine restarts are graceful-first, never casual

Engine control is the one capability that can drop live traffic. Always prefer the graceful operation — CloudCX SBC reload or CloudCX Switch reloadxml drop zero calls — over a full restart. A full restart shows the live call/registration count, requires a typed reason of at least 10 characters and an explicit confirmation, is rate-limited and fully audited with a real-time admin alert, and in production may require a maintenance window and two-person approval. Restarts are ops/admin only; there is no free-form shell — only a fixed allow-list of vetted commands.

CloudCX · Administrator & Training ManualCh. 27 — Glossary & appendices
CloudCXCloudCX Administrator & Training Manual
Ch. 27 · Glossary & appendices

27.4.1 The escalation matrix — who handles what

Use this matrix to route a problem to the right surface and role on the first attempt. The principle: customer-shaped problems go to Support; infrastructure-shaped problems go to Ops; and you reach for the engine controls only when health confirms a node is actually down.

Symptom / signalSurface & roleFirst action
One tenant suspended; an invoice is overdue (§27.3.5)Support supportRecord payment / post a credit → account reactivates. No escalation.
One agent “can’t take calls”Support supportCheck presence (offline?) and SIP registration in the 360 before anything else.
A customer’s message stuck at sentSupport supportExplain the DLR lag; requeue only if the carrier confirms loss. Watch for the terminal state.
One SMPP bind unbound for a carrierOps opsFrom Engine control, rebind that gateway; watch the bind return on the health tile.
Trunk ASR/ACD collapses on one carrierOps opsUse the carrier NOC view + AI explain; fail traffic over via LCR override.
CloudCX Switch/CloudCX SBC node down — calls dropping platform-wideOps ops/adminGraceful first (reload/reloadxml); full restart only with reason + confirm + impact ack.
Suspected security event (IP, auth, exfiltration)Admin + Ops adminReview Security (§25), tighten IP rules, rotate credentials; admins are alerted automatically.
Billing/rating loop erroring in the streamOps → EngineeringFlag-to-TODO from the stream; do not restart the API blindly — engineering owns the loop.
Table 27.7 — Escalation matrix: symptom → correct surface, role and first action.

27.4.2 Worked example — triaging a “calls are dropping” alert

At 14:02 a reseller reports that calls on a Singapore route are dropping. Walk the escalation cleanly:

  1. Open the Ops health dashboard first. Is any node red? Here the SBC and CloudCX Switch tiles are UP, but the carrier NOC shows trunk SG-LCR-2 at 41% ASR with rising NORMAL_TEMPORARY_FAILURE codes. The platform is healthy; one carrier is degraded.
  2. Confirm with the event stream. The stream shows repeated YELLOW entries for SG-LCR-2 and none for the engines. This is a carrier incident, not a platform outage — so it is an Ops action, and emphatically not an engine restart.
  3. Ask the AI to explain (explain-only). The NOC AI summarises the last 30 minutes: “SG-LCR-2 is returning temporary-failure on ~59% of attempts since 13:50; other SG routes are nominal.” It references IDs only and proposes no destructive action.
  4. Apply the right fix: LCR override, not restart. Fail Singapore traffic over to the healthy alternate route (§8.4). ASR recovers immediately for new calls. No graceful reload, no restart — nothing on the platform was wrong.
  5. Record and notify. The override is audited automatically; raise a carrier ticket against SG-LCR-2, and tell the reseller the route was failed over and calls are completing again. If the engines had been at fault, only then would you reach for reload first, and restart last — with the live-call warning acknowledged.
Diagnose before you act

The whole point of the health dashboard and event stream is to tell you where the problem is before you touch anything. A carrier problem is fixed with an LCR override; an engine problem is fixed graceful-first. Restarting an engine to fix a carrier issue would drop live calls and fix nothing.

CloudCX · Administrator & Training ManualCh. 27 — Glossary & appendices
CloudCXCloudCX Administrator & Training Manual
Ch. 27 · Glossary & appendices

27.5 Support contacts and severity targets

When an issue exceeds what the portals can resolve — or you need a human at CloudCX — use the channels below. Classify the incident by severity first; the severity sets both the channel and the expected response. Severities mirror the event-stream classification you saw in §27.4.

SeverityDefinitionChannelTarget first response
Sev-1Critical. Platform-wide outage — calls/messages failing across many tenants, or a confirmed security breach.24×7 emergency hotline + Ops on-call pageImmediate (minutes)
Sev-2Major. One major function or one carrier degraded; significant subset of customers affected.Priority support queue / ops ticketWithin the hour
Sev-3Minor. A single tenant or feature impaired with a workaround available.Standard support portal ticketSame business day
Sev-4Request. Question, configuration help or enhancement request.Support portal / emailNext business day
Table 27.8 — Severity classification and response targets. Confirm the exact contractual SLA with your CloudCX agreement; this is the operational default.
Status page
status.cloudcx.app
Support portal (staff)
cloudcx.app/support
Ops / NOC portal (staff)
cloudcx.app/ops
Support email
Security disclosures
Sev-1 emergency
Ops on-call hotline (24×7)
What to include when you escalate

Every escalation should carry: the affected entity (reseller / tenant / number — never paste full PII or secrets), the object and status you observed (e.g. “invoice overdue”, “SMPP bind unbound”), the time window, the relevant IDs (call UUID, message id, invoice number), and what you have already tried. A well-formed report with IDs is resolved far faster than “it’s broken”.

Security incidents have their own lane

A suspected breach (credential leak, anomalous IP, data exfiltration) is always Sev-1 regardless of customer impact, and goes to [email protected] plus the Ops on-call page. Do not attempt remediation that could destroy evidence (e.g. mass-deleting logs). Tighten IP rules and rotate the affected credentials (§25), preserve the audit trail, and let the on-call lead coordinate.

Try it — build the team’s one-page triage card

Goal: turn these appendices into reflexes. In your sandbox / training tenant:

Success looks like: any new operator can decode an unfamiliar screen and route an incident correctly without asking, using only this chapter.

CloudCX · Administrator & Training ManualCh. 27 — Glossary & appendices
CloudCXCloudCX Administrator & Training Manual
Ch. 27 · Glossary & appendices

27.6 Master glossary

Every term used across the manual, defined once. Acronyms are shown beside the term. Where a term has a dedicated chapter, the section number points to it.

ACD Automatic Contact Distributor
The engine that distributes inbound interactions to the best available agent using queues, skills and a distribution strategy (§10).
ACW After-Call Work
The wrap-up presence state in which an agent completes notes and disposition before becoming available again (§17).
Agent
A front-line user who handles calls and digital conversations from the Agent Desktop (§17).
ASR Answer-Seizure Ratio
The share of call attempts that are answered — a key carrier-quality metric on the NOC (§27.4).
Billing account
One ledger account per platform / reseller / tenant, carrying its balance, currency and mode (postpaid/prepaid) (§23).
Charge
A priced usage line on the ledger; positive, and idempotent on its external id. Moves pending → invoiced (§23).
CDR Call Detail Record
The per-call ledger row: parties, timestamps, duration, billable seconds and hangup cause (§12).
CIDR
An IP range in slash notation (e.g. 203.0.113.0/24) used by IP allow/deny rules (§25).
Console
The CloudCX admin web application at admin.cloudcx.app — the control plane this manual covers.
DID Direct Inward Dialing
An inbound phone number in inventory, assignable to a tenant and a call flow (§8).
DLR Delivery Receipt
The asynchronous carrier confirmation that drives an SMS to delivered/undelivered (§14).
DNC Do Not Contact
A suppression list that blocks outbound contact to a number by channel (§11).
DTMF
Touch-tone digits sent during a call — the dialpad keys, used for IVR navigation (§12).
Dunning
The automated overdue-collections workflow: reminders, then suspend/unsuspend on payment (§24).
Entitlement
A capability or limit granted to a reseller or tenant (channels, seats, features) (§5, §6).
FX
Foreign-exchange rates applied when settling across currencies (§24).
CloudCX Switch
The CloudCX media & telephony engine that bridges, routes and records calls (§2, §27.4).
GSM7 / UCS2
The two SMS encodings; they set how many characters fit per segment, and thus price (§14).
Hangup cause
The SIP/CloudCX Switch release reason on a CDR, e.g. NORMAL_CLEARING, NO_ANSWER (§12).
Invoice
A periodic grouping of pending charges into a numbered document; does not itself move the balance (§23).
IVR Interactive Voice Response
The visual call-flow / menu builder that routes inbound callers (§9).
CloudCX SBC
The CloudCX session border controller at the platform edge: registration, routing, anti-flood (§2, §27.4).
LCR Least-Cost Routing
Choosing the cheapest eligible carrier per call, with manual overrides for quality (§8).
Ledger / journal
The append-only list of signed money events with running balances; the authoritative billing history (§23).
MO / MT
Mobile-Originated (inbound, received) and Mobile-Terminated (outbound) SMS (§14).
MFA Multi-Factor Auth
A second sign-in proof — authenticator app or emailed code — on top of the password (§3, §25).
NOC Network Operations Centre
The Ops portal’s live health, event stream and engine control surface (§27.4).
NPS Net Promoter Score
A survey kind measuring likelihood-to-recommend (§20).
Omnichannel
Treating voice and all digital channels as one routed, unified interaction surface (§16).
Postpaid / prepaid
The two billing modes: invoice-then-collect, or collect-then-spend against a balance (§23, §24).
Presence
An agent’s live routing state: available / break / acw / offline (§17, §18).
Queue
A waiting line for one channel with a distribution strategy and skilled membership (§10).
Rate card
A scoped set of per-prefix prices; wholesale (platform) or retail (reseller) (§23).
Reseller
A white-label partner that operates its own branded book of tenants (§5).
CloudCX Media
The CloudCX media engine that relays call audio/video between parties (§2, §27.4).
SBC Session Border Controller
The signalling edge (CloudCX SBC) that secures and routes SIP into the platform (§2).
Sender ID
The originator presented on an SMS — alphanumeric, long code or short code; must be approved (§14).
Settlement
Collecting an invoice — online via Stripe, or manually by recording a wire / credit (§23).
Skill
A competency tag (e.g. billing, Spanish) matched between agents and queues for routing (§7, §10).
SMPP
The protocol binding CloudCX to SMS carriers; a bind is bound or unbound (§14, §27.4).
SMSC
The carrier’s SMS centre that relays a message toward the handset and returns the DLR (§14).
SSE Server-Sent Events
The streaming transport behind the Ops portal’s live event “terminal” (§27.4).
Subtenant
A sub-organisation under a tenant, with its own scoped administrator (§6).
Tenant
A customer organisation on the platform — the unit that owns users, numbers and channels (§6).
Tier-1 / Tier-2
Support escalation levels: front-line, then deeper specialist support (§27.4, §27.5).
TL Team Leader
A supervisor role with live monitoring and quality tooling (§18, §19).
Wallboard
The supervisor’s live view of queues, agents and presence (§18).
Webhook
An outbound HTTP callback CloudCX sends on events (e.g. a customer DLR relay) (§14).
Wrap-up
See ACW — the post-interaction work state (§17).
End of the manual

That completes the CloudCX Administrator & Training Manual. You now have the full path — from signing in (§3), through tenancy, voice, every channel, supervision, AI, contacts and billing, to the operations and reference material in this chapter. Keep §27.3 (status reference) and §27.4 (escalation) within reach: between them they answer the great majority of day-to-day operational questions. Welcome to running CloudCX.

CloudCX · Administrator & Training ManualCh. 27 — Glossary & appendices
↓ Download PDF