Docs · Account
Clients and portals (Agency add-on).
The Agency add-on turns Ciela into the operating layer for your client roster. Add each client you've sold a Library agent to, connect their deployed agents (Ciela wires most of this for you now, see step 3), and watch performance roll up automatically. Each client also gets a public branded portal at /portal/{slug} so they can see exactly what the agents did for them, without you having to assemble a deck every month. If you connect an Agency add-on custom domain, portal links use that domain too.
Quick facts
- Plan
- Agency add-on
- Lives at
- /dashboard/clients
- Per-client URL
/api/agent-callback/{slug}, 16 hex chars- Per-client secret
- 48 hex chars, sent as
X-Webhook-Secret - Public portal
/portal/{slug}, read-only, no login, custom-domain aware- Idempotency
- Send
external_idand retries collapse to one row
Model
Three concepts to keep straight:
- ClientAn end client of your agency, not a Ciela user. They never log in. You add them at /dashboard/clients with their name, primary contact, and monthly retainer. The server mints a unique slug + webhook secret for them on creation.
- DeploymentOne Library agent you've installed for this client. Receptionist, lead reactivation, quote generator, missed-call text-back, or custom. The deployment is your bookkeeping record, the agent itself runs in the client's n8n + Vapi (not in Ciela). Status flips between live, paused, retired.
- EventA performance datapoint POSTed by a deployed agent. Call answered, lead captured, appointment booked, message sent, message received, quote sent, or custom. Reports speak in concrete agent activity, not revenue.
Walkthrough
Add the client
Click Add client at /dashboard/clients. Name is required. Primary contact, contact email, and monthly retainer are optional, all editable later.
On save you land on the detail page where the unique POST URL and webhook secret are already provisioned and visible. Copy both. Treat the secret like any production credential, if it ever leaks, hit Rotate secret on the detail page to mint a fresh one in place. No need to recreate the client, your events and deployments stay intact.
Record the deployments
For every Library agent you install in this client's infrastructure, hit Record deployment. Pick the agent type, give it a name your client will recognize (eg "Acme Front Desk"), and add any setup notes (Vapi assistant id, n8n flow URL, GHL pipeline id) you want to find again later.
Deployments are pure bookkeeping, you can ship the agent to the client either way, but logging it here lets events attribute to a specific install on the rollup and the portal. If you retire an agent, set its status to retired rather than deleting, so historical metrics keep their attribution.
Connect the metrics (mostly automatic now)
Most of the agent types wire themselves to this client when you set them up, no copy-paste needed:
- Live chat widgetIn Chat Widgets, set "Link to client" to this client (and optionally a specific deployment). The hosted widget then posts
conversation_handled,lead_captured, andappointment_bookedon its own. Nothing to wire, it runs inside Ciela. - Voice receptionist / lead reactivationWhen you provision a voice agent for this client, Ciela points the assistant's end-of-call report straight at this client. Calls land as
call_completed, and a booking confirmed on the call lands asappointment_booked. - n8n workflows (quote, missed-call, etc.)In the Library, pick this client under "Wire this build to a client" before you download. The workflow arrives with a pre-filled "Report to Ciela" node carrying this client's URL + secret, you just drag one wire from your success step (booking saved, quote sent) into it.
For anything custom, or to fire extra events, POST to the client-specific URL yourself with the secret in the X-Webhook-Secret header, on whatever moments matter: every call completed, every booking confirmed, every lead written to the CRM.
Minimum body:
POST /api/agent-callback/<slug>
X-Webhook-Secret: <secret>
Content-Type: application/json
{
"event_type": "appointment_booked",
"deployment_id": "<optional, from /dashboard/clients/[id]>",
"external_id": "<optional, your idempotency key>",
"metadata": { "customer": "Jane", "service": "Cleaning" }
}Send external_id if the runtime might retry (n8n's retry-on-fail, Vapi tool retries). Duplicate POSTs with the same external_id return { ok: true, duplicate: true } and collapse to one row, so you never double-count.
Send deployment_id to attribute the event to a specific agent install. Useful when one client has two receptionists running side by side, otherwise leave it null and the event still rolls up at the client level.
Watch metrics roll up
Back at /dashboard/clients the topline shows active clients and total events in the last 30 days. Each client row shows their own 30-day event count, live deployments, and the retainer amount you set for them (a per-client note, not rolled up across the roster).
Open a client to see the per-type breakdown, the latest events with metadata, and the deployment list with per-agent statuses you can flip in place.
You also get an email the moment a deployed agent books an appointment or sends a quote for a client, so a win pings you without you having to open the dashboard.
Share the branded portal
Hit Open client portal on the detail page, or send your client the URL at /portal/{slug}. It's public, read-only, no login. When you have a connected custom domain, the detail page copies the portal link on that domain instead of ciela.ai. They see the last 30 days of events, the agents running for them, and recent activity.
Ciela's brand is nowhere on the portal. It renders under your agency's brand fields (name, logo, color) from your profile, with per-client logo and color overrides if you set them. Set yours under your account settings before sending the first portal link.
What you see on the roster
The top of /dashboard/clients shows two rollup cards across every client at once, so you can answer "how is my book of business doing?" without opening a client.
- Active clientsCount of clients with
status = active. Total roster count (including paused + churned) shows below. - Events (30d)Webhook events received across every deployment in the last 30 days. The denominator behind whether your AI agency is actually delivering volume to clients.
Each row in the roster shows that client's own retainer amount (a self-reported number you set per client, for your own bookkeeping), live vs total deployments, and 30-day event count. Retainer is per-client context, not rolled up across the roster, because the number is operator-typed and shouldn't be confused with billing-grade MRR. Click any row to open the per-client detail.
Tune a live agent (Production Agent Tuning)
Once a Theo Production voice agent is live for a client, you can change how it behaves in place, without re-provisioning or re-sending the call link. Do it from Ciela chat ("open Bella Nails' receptionist, it keeps booking acrylic fills as full sets, fix it and patch it live") or from the Tune panel on the client's deployment row. Ciela rebuilds the assistant's system prompt and PATCHes the live Vapi assistant in place, keeping its booking and availability tools wired, then records a version you can review and suggests a phrase to retest with.
- What you controlThe prompt layer only: behavior profile and tone, service rules (what the agent asks before booking), FAQ answers, pricing guardrails, and escalation rules. Edit them as plain rules in the Rules / FAQ / Pricing / Escalation tabs, or describe the fix to Ciela in chat.
- Patch liveRebuilds the prompt and updates the live Vapi assistant in place. Every patch is logged under the Versions tab with what changed and whether the live update succeeded.
- TestHands you a suggested scenario to retest the agent with and opens the assistant in Vapi so you can run the call.
Ciela tunes the prompt, n8n runs the backend. Calendar availability, booking, CRM updates, SMS, quote math, and routing live in the client's n8n workflow, and Ciela does not edit them. If the problem is a backend failure (Google Calendar auth, a webhook 404, texts not sending, the wrong calendar connected, a quote total calculated wrong), that's a fix in n8n, not a prompt patch, and Ciela will say so and point you to the n8n workflow link instead of patching the prompt. Use the Integrations tab to store the n8n workflow URL and jump to it.
Set your agency brand
The public portal at /portal/{slug} renders under your agency's brand, not Ciela's. Set this once under account settings, Brand tab and every client portal you share from then on inherits it.
- Agency nameRendered in the portal footer as "Report by <Agency name>". Leave blank for a generic "Performance report" footer.
- LogoUpload directly from your machine, square 256x256 works fine. Falls back as the header image when a client has no per-client logo of their own. JPG, PNG, WebP, SVG, or GIF up to 2 MB.
- Brand colorAny CSS color (hex, rgb, hsl, named). Drives the accent on metric numbers, agent cards, and the activity feed inside the portal. Defaults to champagne (
#d4a574) if unset.
Per-client overrides: each client row has its own optional logo_url and brand_color. If set, the portal uses the client's override for the avatar and accent; if null, it falls back to your agency-level brand. Use overrides when a client wants their own brand on the report (eg an enterprise client who insists on their own logo). Otherwise leave them null and let the agency brand carry through.
Whitelabel your demos: the same tab has an Agency add-on switch, "Remove 'Powered by ciela.ai' from demos". Flip it on and the Ciela mark disappears from every prospect-facing live demo: the hub at /demo/{slug}, where the voice agents run inline, and the on-site chat widget. Set an agency name too and those surfaces read "Built by <your agency>" instead. Base Ciela AI operators ship Ciela-branded demos, no "Built by" line of their own and the Ciela mark stays; putting your brand on demos and stripping the mark is what the Agency add-on buys.
Theme the hub: the Brand tab also has a full theme editor for the demo hub at /demo/{slug} with a live preview: light or dark, the page background, accent color, a brand display font, separate light/dark logo slots, favicon, social image, and rewritten headline / disclaimer / footer copy. Each agent's photo can be swapped or hidden, and the two voice agents can use any voice from the standard demo voice set (no added cost). Leave anything default to keep the Ciela look for that piece.
Your own domain: connect a subdomain you own (eg demos.youragency.com) in the same tab and every demo hub plus every client report serves from your domain with automatic HTTPS, no ciela.ai anywhere. You add one CNAME record; your existing site and email are untouched. Use a subdomain, not your root domain. The default for every tier stays ciela.ai/demo/{slug} and ciela.ai/portal/{slug} until you connect one.
Filtering the detail view
The per-client detail page is where you have monthly review conversations. Three controls narrow what you're looking at:
- Date rangePill toggle at the top:
7 days,30 days,90 days. Drives the metrics rollup card (Events count, type breakdown). The activity feed itself always shows newest first regardless of range, so you can still see what just happened. - Event-type chipsClick
Calls · 22,Appointments · 21, etc. to narrow the activity feed to just that event type. Counts on the chips reflect the current date range. Only types that actually have at least one event in the window appear, no empty filters. - Per-agent chipsSurface only when a client has more than one deployment. Click an agent name to scope the feed to events that deployment posted (events without
deployment_idattribution are excluded when this filter is active). - Load 50 moreThe feed starts with the newest 50.
Load 50 morepaginates backwards using anoccurred_atcursor. Works with filters applied: paginated pages respect the current chip selection. - Clear filtersA single chip appears when any type or per-agent filter is active. One click resets both back to All / All agents.
Monthly summary (the portal hero)
The detail page also drives the narrative that opens the public portal. Claude reads the client's event ledger + active deployments for a period you pick, writes a 60-120 word summary in the agency voice, and hands it back to you to edit before publishing. The end client opens /portal/{slug} and sees the story you approved sitting above the metrics, not a raw activity log. Saved summaries replace the old activity-feed approach to monthly reporting.
- Pick a periodDropdown next to Generate summary:
This month,Last month,Last 30 days,Last 90 days. Drives which events Claude reads. The period label (eg "January 2026") is rendered on the portal next to the summary so the client knows what window they're looking at. - GenerateOne Haiku call, a few cents per generate. Claude reads the events for the chosen period plus the previous window of the same length so it can speak to trend ("calls trended up vs the prior month"). Returns a one-or-two-paragraph draft in plain prose, no markdown, no em dashes, no platform names. You see it as an editable text field, not auto-published.
- EditThe draft is operator-editable. Tighten the wording, swap a number, change the closing sentence, anything. The agency voice is yours to set, Claude just gives you a starting point that's grounded in the actual ledger so you're not staring at a blank box.
- PublishSaves
summary_text+summary_period_labelto the client row and stampssummary_generated_atserver-side. From that moment, anyone with the portal URL sees it at the top of the report. Until you publish, the portal renders without a summary, just the metrics carry the page. - RegenerateGenerating a new summary does not auto-replace the published one, you have to edit + publish again. So the client never sees a draft you didn't sign off on. The detail page shows a 'Generated X days ago' hint next to the saved summary so you know when it's time to refresh.
- ClearEmpty the text field and save to wipe the summary off the portal. The portal renders again without the hero, metrics only. Useful when a client period is too soft to summarize, or when you're between months.
What Claude is told NOT to do: mention Ciela, n8n, or Vapi (the client doesn't care about your stack), use em dashes or markdown, speculate about reasons for changes the data can't prove, or sign off with a name (the portal renders your agency name separately).
What Claude does: lead with the headline metric, name 2-3 specific event counts, include attributed dollar value if non-zero, and close with a forward-looking sentence grounded in what was observed. The voice is third-person factual ("Your AI receptionist answered 47 calls this month"), not first-person agency-speak.
Lifecycle and statuses
Clients and deployments each have their own status field. Use them to keep historical metrics intact while reflecting current reality.
- Client: activeRetainer is being paid, agents are deployed and running. Counts toward the roster's Active clients rollup.
- Client: pausedTemporary hold (client took a break, frozen for a season, mid-pivot). Stays on the roster, drops out of the Active count. Set this when you want metrics history preserved but no longer want the client counted as live.
- Client: churnedRelationship ended. Stays on the roster as historical record. Don't delete the client unless you also want to lose every event ever posted for them, the cascade is aggressive on purpose to keep tables clean.
- Deployment: liveAgent is currently running in the client's infrastructure and posting events. Default on creation.
- Deployment: pausedTemporarily off (maintenance, awaiting a client config change). Events you POST while paused still land in
client_events, the status is informational. Flip back to live when you resume. - Deployment: retiredPermanently decommissioned. Flip to this instead of deleting the row when an agent install ends, otherwise the events that posted under it lose their attribution (the
deployment_idFK isON DELETE SET NULL, so the events themselves survive but the link is gone).
Event types
- call_answeredAgent picked up an inbound call (Vapi assistant or n8n handoff).
- call_completedCall wrapped up. Use this for end-of-call metrics if your runtime distinguishes pick-up from completion.
- conversation_handledChat conversation handled. Use this once per concluded thread, not once per message.
- lead_capturedA new lead got written to the CRM. Add metadata to identify source (form, missed-call recovery, etc.).
- appointment_bookedCalendar event created. The agent booked a real appointment.
- message_sentOutbound text or chat message dispatched by the deployed agent.
- message_receivedInbound message handled by the deployed agent.
- quote_sentQuote / estimate delivered to a prospect by the deployed agent.
- customAnything else. Put a descriptive label in metadata so the activity feed reads sensibly.
Per-client URL + secret model
Every client gets a separate slug + secret. Compromising one client's secret does not affect any other client. Posting to client A's URL with client B's secret returns 404, indistinguishable from a typo on the slug, so blind guessing yields no signal.
Slugs are 16 hex chars, secrets are 48 hex chars, both generated server-side at creation time and stored on the client row. They don't expire. If a secret leaks, hit Rotate secret on the client detail page: it mints a fresh 48-hex secret in place and the old one stops working immediately. The slug and portal URL stay the same, so you only update the X-Webhook-Secret in the deployed flow, your events and deployments are untouched.
Privacy notes for the portal
- What the client seesTheir name, your agency brand, aggregate counts by event type, the deployments running for them, and per-event metadata you chose to send. Nothing about your other clients, nothing about your retainer.
- Metadata is visibleAnything you put in the
metadataobject on the webhook event shows up on the portal feed. Don't post PII you don't want the client to see. The portal is effectively a public URL guarded only by the slug's non-enumerability. - AuthenticationNone. The slug is the secret. Treat the portal URL like a shared Google Doc link, share it deliberately. Rotating the webhook secret stops anyone posting events with the old one but does not change the portal URL, to take a portal offline entirely you delete the client.
Common failure modes
401 Missing X-Webhook-Secret: the runtime sent the POST without the header. Some no-code tools strip custom headers, in that case put the secret in the JSON body as "secret" instead.
404 Not found: slug or secret is wrong. We collapse "wrong slug" and "wrong secret" into the same response so attackers can't tell which factor failed. Copy both fresh from the client detail page.
400 Invalid event_type: the body did not include one of the eight allowed values. Use custom for anything that doesn't fit.
No events showing up: confirm the POST returned { ok: true } in the runtime's logs. Successful posts always include an event_id in the response. If you see duplicate: true, that event already landed under the same external_id.
Need help?
Stuck wiring the webhook from n8n or Vapi? Ask Ciela for a copy-paste config for your specific stack, or write to support@ciela.ai and we'll jump on the connection with you.
Keep reading