Aventora CRM — complete guide
Single reference for aventora-crm (Twenty fork): deployment, upgrade, provisioning/SSO, industry presets, workspace color themes, Sales Cockpit, TIPS sync, and troubleshooting.
Audience: operators, integrators, client backend developers, sales ops.
Laravel custom integration (no plugin): CRM_LARAVEL_INTEGRATION.md — provisioning, SSO, embedded navigation, avatar.
Related platform docs: API_SECURITY_MODEL.md, platform-api-keys.md, CRM_OUTBOUND_EMAIL_TEMPLATES.md
Repo: aventora-crm — deployment assets in docker/, code customizations in packages/twenty-server/src/engine/core-modules/.
Table of contents
- Concepts
- Environment variables
- Deployment and upgrade
- Admin CLI commands
- Provisioning and SSO
- Embedded page mode (host-driven navigation)
- Laravel (custom integration): CRM_LARAVEL_INTEGRATION.md
- Workspaces, users, and industry presets
- Sales Cockpit
- Demo workspace provisioning
- TIPS Services sync
- Laravel plugin
- Contacts and workspace API keys
- Webhooks
- Troubleshooting
- Code map
- Changelog
Concepts
| Concept | CRM meaning | API surface |
|---|---|---|
| User | Person who can log in to a workspace | POST /auth/provision/* on apex CRM_API_URL |
| Contact | person record in the People module | POST /rest/people on workspace host with workspace API key |
Provisioning and SSO use the apex server. Contact writes use https://{subdomain}.{crm-host}. Do not send /rest/people with the provisioning secret.
Environment variables
aventora-crm (server)
| Variable | Required | Description |
|---|---|---|
PROVISIONING_SECRET | Yes | Bearer token for /auth/provision/* (server-side only) |
APP_VERSION | Yes | Valid semver (e.g. 1.20.0); drives workspace migrations. Never leave empty. |
FRONTEND_URL | Multi-workspace | Apex URL, e.g. https://crm.aventora.ai |
SERVER_URL | Yes | API base; usually same as FRONTEND_URL |
IS_MULTIWORKSPACE_ENABLED | Production | true for {subdomain}.crm.aventora.ai |
AVENTORA_BASE_URL | Hub integration | Engagement Hub API base URL |
AVENTORA_WEBHOOK_SECRET | Recommended | Validates X-Aventora-Webhook-Secret on inbound webhooks |
AVENTORA_HUB_AUTO_PROVISION_ENABLED | Optional | When true, enqueue Hub/domain provisioning after CRM workspace or member provisioning |
AVENTORA_HUB_PROVISION_CALLBACK_URL | With auto-provision | Assistant URL, e.g. http://hub:8010/internal/crm-hub-provision |
AVENTORA_HUB_PROVISION_CALLBACK_SECRET | With auto-provision | Shared Bearer secret with Assistant (CRM_HUB_PROVISION_CALLBACK_SECRET) |
Per workspace (not env): AVENTORA_API_KEY in Settings → Applications → Aventora Phone. AVENTORA_PERSON_WALL_ENABLED in Settings → Applications → Aventora (empty = inherit; false = opt out when PERSON_WALL=true).
Person wall (optional, server)
| Variable | Default | Description |
|---|---|---|
PERSON_WALL | false | Kill switch; when true, members see only their own Person records unless workspace opts out |
PERSON_WALL_WORKSPACE_IDS | empty | Comma-separated workspace UUID allowlist (empty = all workspaces) |
PERSON_WALL_WORKSPACE_ID | empty | Deprecated single-UUID allowlist |
PERSON_WALL_EXCLUDE_SYSTEM_RECORDS | false | Hide integration-created persons from scoped members |
PERSON_WALL_DEBUG | false | Log PersonWall scope decisions |
Rollout: workspace:sync-aventora-variables, then workspace:backfill-person-created-by. See Person wall (optional, server).
Calling app (integrator / admin / domain-chatbot)
| Variable | Required | Description |
|---|---|---|
CRM_API_URL | Yes | Apex URL for /auth/provision/* |
CRM_PROVISIONING_SECRET | Yes | Must match CRM PROVISIONING_SECRET |
CRM_PUBLIC_BASE_URL | Contacts / URLs | Defaults to CRM_API_URL |
CRM_WORKSPACE_API_KEY | Contact writes | Workspace-scoped token for /rest/* |
Laravel uses AVENTORA_CRM_URL and AVENTORA_CRM_PROVISIONING_SECRET.
TIPS (optional, server + worker)
| Variable | Required | Default | Description |
|---|---|---|---|
TIPS_CLIENT_ID | For sync | — | OAuth client id |
TIPS_CLIENT_SECRET | For sync | — | OAuth secret |
TIPS_API_BASE_URL | No | https://tipsadvisors.tipservices.ca/api | Include /api |
TIPS_SYNC_WORKSPACE_ID | For sync | — | Single workspace UUID |
TIPS_SYNC_ENABLED | No | false | Enable scheduled cron |
TIPS_SYNC_CRON_PATTERN | No | 0 * * * * | BullMQ cron (hourly) |
Deployment and upgrade
Production (Docker)
Containers run migrations and upgrade on startup (packages/twenty-docker/twenty/entrypoint.sh) unless DISABLE_DB_MIGRATIONS=true:
yarn command:prod cache:flush
yarn command:prod upgrade
yarn command:prod workspace:sync-aventora-sales-person-fields
yarn command:prod cache:flush
Manual upgrade after pulling a new image:
cd /path/to/compose # directory with docker-compose.yml and .env
docker compose pull
docker compose up -d --force-recreate server worker
# Or run explicitly:
docker compose exec server sh -c \
'yarn command:prod cache:flush && yarn command:prod upgrade && yarn command:prod cache:flush'
docker compose exec server yarn command:prod workspace:sync-aventora-variables
Health check:
curl -sf https://crm.example.com/healthz
Keep the worker container running for BullMQ (TIPS cron, outbound jobs).
Development
npx nx run twenty-server:platform:upgrade
Equivalent manual steps: build → cache:flush → upgrade → workspace:sync-aventora-sales-person-fields → cache:flush → workspace:sync-aventora-variables.
What upgrade does
- Pending TypeORM core migrations
- Workspace-level data migrations for current
APP_VERSIONminor - Updates
core.workspace.versionper workspace
Check workspace versions:
SELECT id, version, "displayName", "activationStatus"
FROM core.workspace
ORDER BY version NULLS FIRST;
New deploy checklist
- Update
.env(APP_VERSION, URLs, secrets) docker compose up -d(server runs migrations + upgrade + cron register)- Confirm
curl …/healthzand worker Up workspace:sync-aventora-variables- Existing workspaces with an industry preset:
workspace:reapply-industry-preset(seeds new[preset:…]workflows and cockpit rules without changing preset id/profile) - Review Settings → Workflows and Sales Cockpit → Automation Suggestions; activate templates you want live
Further deployment paths: Docker Hub deploy, Deploy on VPS.
Admin CLI commands
Run in production:
docker compose exec server yarn command:prod <command> [options]
Dev:
npx nx run twenty-server:command -- <command> [options]
Quick index
| Command | Purpose |
|---|---|
upgrade | Primary migrator — run on every deploy |
cache:flush | Flush Redis; run around upgrade |
workspace:sync-aventora-variables | Ensure Aventora app vars on all workspaces (non-destructive) |
workspace:sync-aventora-sales-person-fields | Sales Cockpit Person fields (auto after upgrade in Docker) |
workspace:seed-aventora-sales-cockpit-demo | Demo signals on existing people |
workspace:apply-industry-preset | Apply preset to existing workspace (first time or with overrides) |
workspace:reapply-industry-preset | Re-apply preset from each workspace’s stored industryPreset / industryProfile |
tips:bootstrap-fields | TIPS fields + hub role + RLS |
tips:sync | One-off TIPS inbound sync |
cron:register:all | Register background crons (incl. TIPS) |
workspace:backfill-person-created-by | Backfill person.createdBy for PERSON_WALL readiness |
Demo sales signals
After upgrade and person-field sync:
docker compose exec server yarn command:prod workspace:seed-aventora-sales-cockpit-demo
Idempotent: adds demo signals on existing people in active workspaces. Does not create or delete CRM records.
Local dev (destructive full reset + demo signals):
npx nx database:reset twenty-server
Industry preset on existing workspace
Re-apply after upgrade (uses each workspace’s stored preset; no UUID or preset flags required):
docker compose exec server yarn command:prod workspace:reapply-industry-preset
Optional single workspace: add -w <workspace-uuid>. Idempotent: appliers only add missing [preset:…] workflows, rules, fields, etc.
First-time apply or change preset (requires workspace id; use --force if already applied):
docker compose exec server yarn command:prod workspace:apply-industry-preset \
-w <workspace-uuid> \
--industry-preset insurance \
--industry-profile general_insurance_advisor
Check state:
SELECT id, "displayName", "industryPreset", "industryProfile", "presetAppliedAt"
FROM core.workspace WHERE id = '<uuid>';
Provisioning and SSO
All routes: Authorization: Bearer <PROVISIONING_SECRET> on CRM_API_URL.
| Method | Path | Purpose |
|---|---|---|
POST | /auth/provision/resolve | Find or create workspace + user |
POST | /auth/provision/login-token | SSO token + workspaceUrl; optional page, avatar |
POST | /auth/provision/person-ownership-context | Resolve workspace member + Person Wall scope for Hub contact creates |
POST | /auth/provision/user | Add user to existing workspace |
POST | /auth/provision/move-user | Move user membership from one workspace to another (access only) |
POST | /auth/provision/workspace | Create or reuse named workspace (supports industryPreset) |
GET | /auth/provision/workspace?subdomain= or ?displayName= | Lookup workspace |
GET | /auth/provision/workspaces?activationStatus=ACTIVE | List workspaces with hubConnected, adminEmail, assignedDomain |
POST | /auth/provision/link-workspace | Wire Hub↔CRM; returns twentyApiKey |
POST | /auth/provision/demo-tenant | Demo workspace + API key + Phone wiring |
POST | /auth/provision/sync-cockpit-action-mappings | Push Sales Cockpit mappings |
GET | /auth/provision/industry-preset-catalog | Preset list for UIs |
DELETE | /auth/provision/workspace?subdomain= | Hard-delete workspace (demo teardown) |
SSO sequence
sequenceDiagram
participant Browser
participant App as Calling App Backend
participant CRM as aventora-crm
Browser->>App: Open CRM
App->>CRM: POST /auth/provision/resolve
CRM-->>App: workspaceId, userId, subdomain
App->>CRM: POST /auth/provision/login-token
CRM-->>App: workspaceUrl
App-->>Browser: Redirect to workspaceUrl
All provisioning calls must run on the backend. The browser only receives the final workspaceUrl.
Resolve
POST /auth/provision/resolve
{
"email": "agent@example.com",
"firstName": "Alex",
"lastName": "Agent",
"tenantSubdomain": "acme",
"tenantDisplayName": "Acme Inc",
"avatar": "https://cdn.example.com/avatars/user.png"
}
tenantSubdomain optional — omit for personal workspace mode.
Optional avatar — same rules as login-token.
Response includes wasCreated: { workspace, user }.
Login token
POST /auth/provision/login-token
{
"workspaceId": "...",
"email": "agent@example.com",
"firstName": "Alex",
"lastName": "Agent",
"engagementInitiatorPhone": "+15551234567",
"page": "/objects/people",
"avatar": "https://cdn.example.com/avatars/user.png"
}
page is optional — see Embedded page mode.
avatar is optional — a publicly accessible http or https image URL. CRM downloads the image, stores it as the workspace member profile picture, and uses it in the user profile and anywhere the member avatar is shown. Invalid URLs return HTTP 400. If the URL is unreachable or not an image, provisioning continues without an avatar (logged server-side).
Creates user + membership if missing. Redirect browser to workspaceUrl.
The returned workspaceUrl always includes aventoraSso=1 (CRM hides Log out for SSO sessions). When page is set, the URL also includes returnToPath and aventoraEmbedded=1.
Person ownership context (Hub contact creates)
Engagement Hub attributes CRM people to the initiating user by calling this endpoint before POST /rest/people.
POST /auth/provision/person-ownership-context
{
"workspaceId": "...",
"email": "agent@example.com",
"engagementInitiatorPhone": "+15551234567"
}
At least one of email or engagementInitiatorPhone is required. Phone matches userWorkspace.aventoraEngagementInitiatorPhone (same normalization as login-token provisioning). If the user is not yet a workspace member, CRM provisions them with the Aventora User role (same as SSO).
Response:
{
"workspaceMemberId": "...",
"displayName": "Jane Agent",
"personWallScopeFieldName": "tipsAgentId",
"personWallScopeValue": 42
}
personWallScopeFieldName / personWallScopeValue are included when Person Wall is enabled and PERSON_WALL_FIELD_NAME includes a legacy numeric field (e.g. TIPS tipsAgentId or tipsAgentId,createdBy). Hub copies both createdBy.workspaceMemberId and the scope field onto new people.
Engagement Hub env (same values as domain-chatbot provisioning):
| Variable | Description |
|---|---|
CRM_API_URL | Apex URL for /auth/provision/person-ownership-context |
CRM_PROVISIONING_SECRET | Must match CRM PROVISIONING_SECRET |
Domains must have crm_workspace_id in domain account_settings (set when CRM is enabled in aventora-admin).
Embedded page mode (host-driven navigation)
Use this when your app (not CRM) owns navigation — e.g. a sidebar or submenu in aventora-admin that opens CRM in an iframe or new window.
| Mode | page parameter | CRM behavior |
|---|---|---|
| Full CRM | Omitted | Normal CRM with left navigation and mobile bottom nav |
| Embedded page | Provided | Left nav and mobile bottom nav hidden; user lands on the requested screen |
In embedded mode:
- Log out is hidden in CRM (user exits via your app)
- Navigation is your responsibility — each submenu item should trigger a new SSO request with a different
page - The user may still follow in-page links (e.g. open a record); CRM nav stays hidden for that session
How to call SSO with page
Laravel host app (authenticated):
GET /crm/sso?page=/objects/people
| Query param | Required | Description |
|---|---|---|
tenant | No | CRM workspace subdomain. Omit for the user’s personal workspace. |
page | No | Full internal CRM path (see Available pages). Omit for full CRM. |
avatar | No | Public http/https image URL for the user’s CRM profile picture. Omit to leave unchanged. |
Direct provisioning API (server-to-server):
POST /auth/provision/login-token
{
"workspaceId": "uuid",
"email": "user@example.com",
"page": "/objects/people",
"avatar": "https://cdn.example.com/avatars/user.png"
}
The same optional avatar field is supported on POST /auth/provision/resolve and POST /auth/provision/user.
SSO response (Laravel GET /crm/sso)
{
"url": "https://{subdomain}.crm.example.com/verify?loginToken=...&aventoraSso=1&returnToPath=%2Fobjects%2Fpeople&aventoraEmbedded=1",
"loginToken": "...",
"expiresAt": "2026-...",
"workspaceId": "...",
"subdomain": "...",
"userId": "...",
"page": "/objects/people"
}
| Field | Description |
|---|---|
url | Open this URL in iframe, popup, or redirect |
loginToken | Short-lived token (also inside url) |
expiresAt | Token expiry (ISO timestamp) |
workspaceId | CRM workspace ID |
subdomain | Workspace subdomain |
userId | CRM user ID |
page | Echo of requested page (only when provided) |
Your frontend should open url — no extra client-side URL building is required.
page parameter rules
- Format: full internal CRM path, starting with
/- Valid:
/cockpit,/objects/people,/settings/profile - Invalid:
cockpit,https://...,//evil.com
- Valid:
- Validation: invalid paths return HTTP 400 from
login-token - URL encoding: when passing
pageas a query param, encode it:page=%2Fobjects%2Fpeople - Permissions: a valid path may still show an empty or restricted view if the user lacks CRM permissions
- Custom objects: use
/objects/{pluralName}where{pluralName}is the object’s API plural name in that workspace
Blocked paths (return 400)
Do not use auth, onboarding, or sign-up routes:
/welcome,/verify,/verify-email/create/*,/invite-team,/plan-required,/book-call*/reset-password/*/(root alone)
Available page values
Main app screens
| Screen | page value | Notes |
|---|---|---|
| Sales Cockpit | /cockpit | Default home when Sales Cockpit is enabled |
| People (list) | /objects/people | Standard CRM object |
| Companies (list) | /objects/companies | Standard CRM object |
| Opportunities (list) | /objects/opportunities | Standard CRM object |
| Tasks (list) | /objects/tasks | Standard CRM object |
| Notes (list) | /objects/notes | Standard CRM object |
| Dashboards (list) | /objects/dashboards | If enabled in workspace |
| Workflows (list) | /objects/workflows | If enabled in workspace |
Record detail pages (optional deep links)
Pattern: /object/{singularName}/{recordId}
| Screen | page value |
|---|---|
| Person record | /object/person/{uuid} |
| Company record | /object/company/{uuid} |
| Opportunity record | /object/opportunity/{uuid} |
Replace {uuid} with the CRM record ID.
Settings screens (prefix /settings/)
| Settings area | page value |
|---|---|
| Profile | /settings/profile |
| Experience (theme/locale) | /settings/experience |
| Connected accounts | /settings/accounts |
| Account emails | /settings/accounts/emails |
| Account calendars | /settings/accounts/calendars |
| Workspace general | /settings/general |
| Data model | /settings/objects |
| Members | /settings/members |
| Roles | /settings/roles |
| Domains | /settings/domains |
| Billing | /settings/billing |
| APIs & Webhooks | /settings/api-webhooks |
| Apps | /settings/applications |
| AI | /settings/ai |
| Security | /settings/security |
| Admin panel | /settings/admin-panel |
| Updates | /settings/updates |
Settings sub-pages with IDs (roles, API keys, etc.) follow the same pattern, e.g. /settings/roles/{roleId}.
Custom workspace objects
/objects/{objectPluralName}
/object/{objectSingularName}/{recordId}
Example: custom object plural deals → /objects/deals
Recommended host app pattern
| Your app menu | SSO call |
|---|---|
| CRM Home (full) | GET /crm/sso |
| Sales Cockpit | GET /crm/sso?page=/cockpit |
| People | GET /crm/sso?page=/objects/people |
| Companies | GET /crm/sso?page=/objects/companies |
| Opportunities | GET /crm/sso?page=/objects/opportunities |
| My profile | GET /crm/sso?page=/settings/profile |
Each click: (1) call /crm/sso with the chosen page, (2) open returned url. Switching sections = new SSO call, not in-CRM navigation.
Embedded vs full CRM
| Feature | Without page | With page |
|---|---|---|
| Left navigation | Visible | Hidden |
| Mobile bottom nav | Visible | Hidden |
| Log out in CRM | Hidden (SSO session) | Hidden (SSO session) |
| Landing screen | CRM default home | Your page path |
| Who navigates | User (CRM nav) | Your app (submenu + SSO) |
Verify embedded SSO
GET /crm/sso→ full CRM with navigationGET /crm/sso?page=/cockpit→ Cockpit only, no navGET /crm/sso?page=/objects/people→ People list, no navGET /crm/sso?page=/welcome→ 400 (invalid path)- Switch submenu → new SSO call with different
page→ correct screen each time
Existing workspace shortcut
When crm_workspace_id is already stored (aventora-admin after CRM enable), skip resolve and call login-token only with that workspaceId.
Add user explicitly
POST /auth/provision/user with workspaceId + email. For SSO, login-token alone is usually enough.
Provision workspace with preset
POST /auth/provision/workspace
{
"adminEmail": "admin@example.com",
"subdomain": "acme",
"displayName": "Acme Insurance",
"industryPreset": "insurance",
"industryProfile": "general_insurance_advisor"
}
Idempotent: if subdomain exists, adds/finds admin user and returns existing workspace.
Workspaces, users, and industry presets
When is a workspace created?
| Flow | Who picks workspace | Creates workspace? |
|---|---|---|
resolve + tenantSubdomain | Client passes subdomain | Yes, if subdomain new |
resolve without tenantSubdomain | CRM per email | Yes, personal workspace if none exists |
login-token with workspaceId | Caller (stored ID) | Never |
provision/workspace or demo-tenant | Explicit API call | Yes, if subdomain new |
TIPS tips:sync | TIPS_SYNC_WORKSPACE_ID env | Never |
| aventora-admin SSO | crm_workspace_id in domain settings | Never at login time |
Resolve decision tree
User already has a workspace membership:
- Returns that workspace (no create)
- If requested
tenantSubdomaindiffers →400
New user + tenantSubdomain provided:
- Subdomain exists → join workspace, create user if needed
- Subdomain missing → create shared workspace + user as admin
New user + no tenantSubdomain:
- Personal workspace for email exists → reuse
- Else → create personal workspace + user
Hard rules:
- One workspace per user through provisioning (multi-membership →
400) - Cannot move user to another workspace via SSO (
400) — usemove-userinstead - User exists with no membership →
400(orphan; manual cleanup)
Move user between workspaces
POST /auth/provision/move-user moves a user's login membership from workspace A to workspace B. It does not migrate CRM records (People, engagements, cockpit history, etc.) — those stay in the source workspace.
Requirements:
- User must belong to exactly one workspace (the
fromworkspace) - Source workspace must have at least one other member (cannot move the only member out)
- User cannot be the only admin in the source workspace unless another admin is assigned first
- Target workspace must be active
POST /auth/provision/move-user
{
"email": "agent@example.com",
"fromWorkspaceSubdomain": "tenant-a",
"toWorkspaceSubdomain": "tenant-b",
"firstName": "Alex",
"lastName": "Agent",
"avatar": "https://cdn.example.com/avatars/agent.png"
}
Use fromWorkspaceId / toWorkspaceId instead of subdomains when IDs are already stored.
Response:
{
"userId": "uuid",
"email": "agent@example.com",
"fromWorkspaceId": "uuid",
"fromSubdomain": "tenant-a",
"toWorkspaceId": "uuid",
"toSubdomain": "tenant-b",
"userRestored": false
}
userRestored is true when the core user row was soft-deleted and restored during the move.
After a successful move, update external systems (crm_workspace_id, SSO login-token workspaceId) to the to workspace. Issue a new login-token for the target workspace on next SSO.
| Situation | Result |
|---|---|
| User not found | 404 |
User not in from workspace | 400 |
User already in to workspace | 400 (use login-token only) |
| User in multiple workspaces | 400 |
Only member of from workspace | 400 |
Only admin of from workspace | 400 |
from and to are the same | 400 |
| Target workspace inactive | 400 |
Industry presets
Presets apply once at workspace activation when industryPreset is set on the workspace row. They add labels, custom fields, views, pipelines, dashboards, CRM workflow templates (DRAFT), and Sales Cockpit automation rules.
Aventora workflow templates (v1)
Industry presets seed inactive Aventora automation in two layers. Templates are not created by upgrade alone on workspaces that already have presetAppliedAt set — run workspace:reapply-industry-preset after deploying a build that includes workflow seeding.
| Layer | Where in CRM | How to find | Default state | User activates |
|---|---|---|---|---|
| Twenty CRM Workflows | Workflows module | Settings → Workflows — names start with [preset:av_wf_…] | DRAFT | Open template → review → Activate |
| Sales Cockpit rules | Core aventoraAutomationRule table | Sales Cockpit (/cockpit) → Automation Suggestions panel | DRAFT / disabled | Enable rule; optional Auto-run when triggered |
Manual CRM workflows also appear on People records (single-record or bulk selection) via the workflow/command menu after activation.
CRM workflow templates (all industries)
| Key | UI name (prefix [preset:…]) | Trigger | Hub action |
|---|---|---|---|
av_wf_manual_engage_person | Engage Person (Aventora) | Manual, single person | Form (channel + instruction) → engage |
av_wf_new_person_say_hello | New Person: Say Hello (SMS) | person.created | SAY_HELLO SMS |
av_wf_new_person_qualify_call | New Person: Qualify (Call) | person.created | CALL_LEAD phone |
av_wf_bulk_sms_checkin | Bulk SMS: Check Interest | Manual bulk people | CHECK_INTEREST SMS |
av_wf_bulk_reconnect | Bulk SMS: Reconnect | Manual bulk people | RECONNECT SMS |
av_wf_bulk_booking_link | Bulk SMS: Send Booking Link | Manual bulk people | SEND_BOOKING_LINK SMS |
Workflow steps use custom action types START_AVENTORA_ENGAGEMENT (single person) and START_AVENTORA_ENGAGEMENT_BULK (bulk). Both call the shared AventoraEngagementExecutionService, which respects doNotContact on the person and uses workspace Aventora app variables + cockpit action mappings.
Cockpit automation templates
| Key | Name | Signals | Default action |
|---|---|---|---|
av_cockpit_missed_call | Missed Call Follow-Up | MISSED_CALL | CALL_LEAD |
av_cockpit_no_reply | No Reply Follow-Up | NO_REPLY | SEND_FOLLOW_UP |
When Auto-run is enabled on an active rule, the cockpit execution path applies cooldown and max executions per lead per day guards before calling Hub.
GraphQL (cockpit rules): updateAventoraAutomationRule — toggle enabled and autoExecute from the Automation Suggestions UI.
Compliance
Auto workflows (person.created, cockpit auto-run) can contact leads without a manual click. Review every template before activation. People marked do not contact are skipped by the execution service.
industryPreset | Example profiles |
|---|---|
real_estate | buyer_agent, listing_agent, broker, … |
insurance | personal_lines, general_insurance_advisor, … |
mortgage | mortgage_agent, broker_owner, … |
financial_advisor | wealth_advisor, practice_owner, … |
generic | default |
Important: POST /auth/provision/resolve does not accept industryPreset. Personal and tenant workspaces created via resolve alone get no industry preset (generic CRM + standard Aventora fields).
To control preset:
- Use
POST /auth/provision/workspaceordemo-tenantwithindustryPreset+industryProfile - Use onboarding UI (self-serve signup)
- Run
workspace:apply-industry-presetafter the fact (first time or to change preset/profile) - Run
workspace:reapply-industry-presetafter upgrades to backfill new template artifacts on workspaces that already have a preset
TIPS: One shared workspace is created with insurance preset before sync. tips:sync only creates users in that workspace — not new workspaces or presets per agent.
Color theme presets
Workspace admins can choose an accent color theme for the whole CRM UI. This is separate from industry presets (which seed CRM data and workflows). Color themes only change accent colors — buttons, links, highlights, and related UI chrome. They do not switch light vs dark mode.
CRM has two independent appearance controls:
| Setting | What it changes | Scope | Where in CRM |
|---|---|---|---|
Color theme (themePreset) | Accent palette (Classic, Ocean, Forest, …) | Entire workspace — all members see the same accents | Settings → Workspace → General → Color theme |
Light / Dark / System (colorScheme) | Light mode, dark mode, or follow OS | Per user — each member chooses their own | Top workspace menu → Theme, or Settings → User → Experience → Appearance |
The workspace dropdown Theme submenu (System / Dark / Light) is the same personal colorScheme setting as User → Experience — not the workspace color theme.
Workspace color theme changes apply immediately for all members after save. Default for new and existing workspaces (post-migration) is classic (Twenty indigo).
Available presets
themePreset value | Label | Accent palette |
|---|---|---|
classic | Classic | Indigo (Twenty default) |
ocean | Ocean | Teal |
forest | Forest | Jade |
sunset | Sunset | Orange |
violet | Violet | Violet |
rose | Rose | Crimson |
midnight | Midnight | Iris |
Valid values are enforced on updateWorkspace; invalid IDs are rejected.
API and storage
- Column:
core.workspace."themePreset"(varchar, default'classic') - Migration:
1782000000002-add-workspace-theme-preset.ts(runs with normalupgrade) - GraphQL:
updateWorkspace(data: { themePreset: "ocean" })on workspace metadata API - Permission:
PermissionFlagType.WORKSPACE(same as workspace name/logo)
Check current value:
SELECT id, "displayName", "themePreset"
FROM core.workspace
WHERE id = '<uuid>';
Programmatic update (admin session or API key with workspace settings access):
mutation UpdateWorkspaceTheme {
updateWorkspace(data: { themePreset: "ocean" }) {
id
themePreset
}
}
Deploy note
After upgrading to a build that includes color themes:
- Ensure core migrations have run (
yarn command:prod upgrade) — addscore.workspace.themePreset. - No extra CLI command is required; admins pick a preset in Settings → Workspace → General.
Color themes and industry preset workflows ship in the same CRM build but are configured independently.
Sales Cockpit
Signal-driven sales layer on Person records: ingest signals → scores → suggested actions → /cockpit dashboard and Person Sales tab.
Enablement checklist
- Deploy with
upgrade(Sales Cockpit migrations) workspace:sync-aventora-sales-person-fieldsandworkspace:sync-aventora-variables- Wire Hub↔CRM:
AVENTORA_API_KEY, webhooks,AVENTORA_WEBHOOK_SECRET - aventora-admin: Enable Aventora CRM; configure cockpit action mappings
- Confirm
IS_SALES_COCKPIT_ENABLEDon workspace - Optional demo data:
workspace:seed-aventora-sales-cockpit-demo
Feature flags
| Flag | Effect |
|---|---|
IS_SALES_COCKPIT_ENABLED | /cockpit nav, default home route |
IS_SUGGESTED_ACTIONS_ENABLED | Reserved |
Dashboard panels
Priority Leads, Suggested Actions, Reconnect Recent, Reconnect Oldies, Stale Leads, Hot Leads, Recently Engaged, Leads Requiring Follow-up, Team Activity, Automation Suggestions (enable preset rules and optional Auto-run when triggered; respects cooldown and daily max per lead).
Signal sources
| Source | How |
|---|---|
| Hub webhooks | Engagement outcomes → signals |
| REST | POST /rest/sales-intelligence/signals |
| GraphQL | createAventoraPersonSignal |
| Demo | workspace:seed-aventora-sales-cockpit-demo |
Cockpit action → Hub mappings
Configured in aventora-admin → Account Settings → CRM Sync when provider is Aventora CRM.
Stored in domain-chatbot twenty_cockpit_action_mappings, synced to CRM app var AVENTORA_COCKPIT_ACTION_MAPPINGS via POST /auth/provision/sync-cockpit-action-mappings.
Hub accepts informational, conversational, appointment_booking, confirmational — not raw cockpit action names like ESCALATE_TO_SALES.
Cockpit APIs
- GraphQL:
aventoraSalesCockpitSummary,aventoraPersonIntelligence, … - REST:
GET /rest/sales-intelligence/cockpit-summary,POST /rest/sales-intelligence/signals - Execute:
POST /rest/aventora/start-engagementwithcockpitActionType
Sales Cockpit troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| "Sales cockpit is not available yet." | GraphQL query failed (not empty data) | Browser DevTools → Network → GetAventoraSalesCockpitSummary → read errors[0].message. Check server logs. |
| SQL error on load (e.g. missing column) | Migrations or person fields not synced | upgrade, workspace:sync-aventora-sales-person-fields |
| Panels show "No leads to show" | Query succeeded, no signals | workspace:seed-aventora-sales-cockpit-demo or ingest signals |
| Execute 400, invalid Hub type | Mappings not synced | Admin → Sync to CRM |
| Feature missing | Flag off | IS_SALES_COCKPIT_ENABLED |
No [preset:…] workflows after upgrade | Workspace already had presetAppliedAt; templates not backfilled | workspace:reapply-industry-preset |
reapply-industry-preset errors on workflow insert | Older workspace workflow metadata | Upgrade to latest build (applier omits legacy actor columns); re-run workspace:reapply-industry-preset |
Debug GraphQL: failed cockpit load always means backend error. Empty data still shows panel grid with "No leads to show."
CRM-first Hub provisioning (existing CRM workspaces)
Automatic (CRM-triggered)
When AVENTORA_HUB_AUTO_PROVISION_ENABLED=true on aventora-crm, provisioning a workspace or adding a workspace member enqueues a BullMQ job that calls Aventora Assistant POST /internal/crm-hub-provision. CRM holds only the callback URL + shared secret; domain-chatbot super-admin credentials and telephony env stay on Assistant.
| CRM env | Assistant env |
|---|---|
AVENTORA_HUB_AUTO_PROVISION_ENABLED=true | CRM_HUB_PROVISION_CALLBACK_SECRET (same value as CRM secret) |
AVENTORA_HUB_PROVISION_CALLBACK_URL | DOMAIN_CHATBOT_*, PROVISION_INBOUND_PHONE, Twilio vars |
- New workspace (via
/auth/provision/*createWorkspaceWithAdmin): jobaction=fullafter ~60s delay (deduped per workspace). - New workspace member: job
action=sync_membersafter ~30s delay (skipped server-side if Hub not linked yet).
Requires CRM worker process running (same as TIPS sync). Failures retry up to 3 times; CRM provisioning itself never fails because of Hub auto-provision.
Manual batch (ops backfill)
When CRM workspaces already exist but domain-chatbot, Engagement Hub, and CRM↔Hub wiring are missing, run the aventora-assistant script from aventora-phone:
cd aventora-phone
python scripts/provision_hub_from_crm_workspaces.py --dry-run
python scripts/provision_hub_from_crm_workspaces.py --execute \
--inbound-phone +14167078887 \
--twilio-account-sid "$PROVISION_TWILIO_ACCOUNT_SID" \
--twilio-api-key "$PROVISION_TWILIO_API_KEY" \
--twilio-api-secret "$PROVISION_TWILIO_API_SECRET"
# Classic auth token mode (instead of API key):
python scripts/provision_hub_from_crm_workspaces.py --execute \
--inbound-phone +14167078887 \
--twilio-account-sid "$PROVISION_TWILIO_ACCOUNT_SID" \
--twilio-auth-token "$PROVISION_TWILIO_AUTH_TOKEN"
Hub stores API key credentials as twilio_account_sid (AC…), twilio_api_key_sid (SK…), and twilio_auth_token (secret). Classic mode uses twilio_account_sid + twilio_auth_token only. Both modes are supported at runtime via create_twilio_client.
The shared --inbound-phone is written to inbound settings and, by default, to all Twilio outbound fields: twilio_phone_number, twilio_sms_number, twilio_whatsapp_number, and display_number (caller ID). Use --twilio-phone-number only when outbound must differ from inbound.
The script:
- Calls domain-chatbot
GET /regenerate-index/provision-crm-workspaces(super-admin; CRM credentials stay on domain-chatbot). - For each disconnected workspace, calls domain-chatbot
POST /regenerate-index/provision-from-crm-workspaceto create the domain, Hub account, shared inbound/Twilio defaults, andlink-workspaceCRM credentials. - For already-linked workspaces, use
POST /regenerate-index/sync-crm-workspace-membersorprovision_hub_from_crm_workspaces.py --sync-members-only --executeto backfill domain-chatbot + Hub users from CRMhubSyncMemberEmails.
Required env (Hub / aventora-phone)
| Variable | Used by |
|---|---|
DOMAIN_CHATBOT_API_URL (or DOMAIN_CHATBOT_URL) | Workspace list + reverse provision API |
DOMAIN_CHATBOT_SUPER_ADMIN_TOKEN or SUPER_ADMIN_USERNAME + SUPER_ADMIN_PASSWORD | domain-chatbot super-admin auth |
CRM_API_URL, CRM_PROVISIONING_SECRET | Person ownership context for Hub-created CRM contacts |
DATABASE_URL (optional) | Hub accounts.domain_name lookup in dry-run summary |
PROVISION_INBOUND_PHONE, PROVISION_TWILIO_ACCOUNT_SID, PROVISION_TWILIO_AUTH_TOKEN or PROVISION_TWILIO_API_KEY + PROVISION_TWILIO_API_SECRET | CLI fallbacks for telephony |
CRM_HUB_PROVISION_CALLBACK_SECRET | Authenticates CRM POST /internal/crm-hub-provision (same value as CRM AVENTORA_HUB_PROVISION_CALLBACK_SECRET) |
CRM CRM_API_URL and CRM_PROVISIONING_SECRET are required on Engagement Hub for person ownership resolution (Hub calls POST /auth/provision/person-ownership-context when creating CRM contacts). They remain required on domain-chatbot for workspace list, link-workspace, and demo provisioning.
Required env (domain-chatbot)
| Variable | Used by |
|---|---|
CRM_API_URL, CRM_PROVISIONING_SECRET | CRM workspace list + link-workspace |
Inbound phone and Twilio credentials are operator-supplied (CLI or env), applied to every workspace on first provision. Per-account overrides are done later in aventora-admin Account Settings.
Prerequisites
- CRM deployed with migrations and
workspace:sync-aventora-variables - domain-chatbot and Engagement Hub running with service keys configured
- CRM workspace subdomain (or
AVENTORA_ASSIGNED_DOMAIN) must be a valid domain slug:[a-z0-9-]{3,30}
Demo workspace provisioning
Ephemeral demos provision a CRM workspace via domain-chatbot + POST /auth/provision/demo-tenant.
domain-chatbot env
| Variable | Description |
|---|---|
CRM_API_URL, CRM_PROVISIONING_SECRET | CRM provisioning |
CRM_PUBLIC_BASE_URL | Workspace URL builder |
CRM_WEBHOOK_SECRET | Must match CRM AVENTORA_WEBHOOK_SECRET |
DEMO_ENABLE_CRM | Default true; false to skip |
HUB_API_URL, HUB_API_KEY | Demo engagement |
CRM endpoints
POST /auth/provision/demo-tenant— workspace +twentyApiKey+ Phone wiring (supportsindustryPreset)DELETE /auth/provision/workspace?subdomain=— teardown
Verification
- Create demo invitation in admin
- Confirm
demo_confighascrm_workspace_id,crm_subdomain - Open demo live page → CRM tab
- Run engagement demo → contact appears in CRM
- On revoke/expiry → workspace deleted
Demo slugs capped at 30 characters for CRM subdomain validation.
TIPS Services sync
Optional: sync TIPS Advisors agents → CRM users; clients → people. Ships on main; enable with TIPS_* env.
Setup (Tip Services)
- Create workspace (onboarding: Insurance Brokerage preset, or
workspace:apply-industry-preset) - Set
TIPS_SYNC_WORKSPACE_IDon server and worker tips:bootstrap-fields(fields + Member (Aventora Hub) role)tips:sync- Optional schedule:
TIPS_SYNC_ENABLED=true, recreate server orcron:tips:sync
Inbound sync
For each TIPS agent with crmEnabled:
ensureUserInWorkspaceForTipsSync— same primitive as SSOfindOrCreateUserInWorkspace(+ password sync, role fromcrmHubEnabled)- Updates workspaceMember
tips*fields - Upserts Person records for clients
TIPS vs client SSO
Client SSO (resolve) | TIPS sync | |
|---|---|---|
| Workspace | Per tenantSubdomain or personal | Fixed TIPS_SYNC_WORKSPACE_ID |
| Creates workspace | Sometimes | Never |
| Industry preset | Only via provision/workspace | On pre-built workspace |
| Trigger | User clicks Open CRM | Cron / CLI |
Verify scheduled sync
docker compose ps worker
docker compose logs server 2>&1 | grep -i TipsSync
docker compose logs worker --since 24h | grep -i tips
Laravel plugin
Building your own Laravel integration? Use CRM_LARAVEL_INTEGRATION.md — the shareable guide for custom HTTP clients (provisioning, SSO,
page,avatar). This section covers the official Composer package only.
Package: aventora-crm/laravel-plugin/
composer require aventora/crm-laravel
php artisan vendor:publish --tag=crm-config
AVENTORA_CRM_URL=https://crm.example.com
AVENTORA_CRM_PROVISIONING_SECRET=...
AVENTORA_CRM_DEFAULT_TENANT=acme # optional shared subdomain
Route: GET /crm/sso → resolve → login-token → open CRM.
Optional query param page — full CRM path for embedded page mode (hides CRM nav, deep-links to screen):
GET /crm/sso?page=/objects/people
GET /crm/sso?tenant=acme&page=/cockpit
Response JSON includes url (ready to open), plus page when requested. See SSO response for field list.
Health check:
php artisan crm:health-check --email=demo@local.test --tenant=acme
Blade:
@include('crm::components.button', ['tenant' => 'acme', 'label' => 'Open CRM'])
Contacts and workspace API keys
Base URL: https://{subdomain}.{crm-host}
POST /rest/people
Authorization: Bearer <CRM_WORKSPACE_API_KEY>
Content-Type: application/json
{
"name": { "firstName": "Jordan", "lastName": "Client" },
"emails": { "primaryEmail": "jordan@example.com", "additionalEmails": [] },
"phones": {
"primaryPhoneNumber": "5551234567",
"primaryPhoneCountryCode": "US",
"primaryPhoneCallingCode": "+1",
"additionalPhones": []
}
}
API keys from link-workspace, demo-tenant, or CRM Settings → API keys.
Webhooks
Hub → CRM: POST https://{subdomain}.{host}/rest/aventora/webhook with X-Aventora-Webhook-Secret.
CRM → Hub: per-workspace AVENTORA_API_KEY + server AVENTORA_BASE_URL.
Troubleshooting
Provisioning / SSO
# 1. Right host?
curl -sS "https://CRM_HOST/healthz"
# 2. Route exists? (expect 401, not 404)
curl -sS -X POST "https://CRM_HOST/auth/provision/resolve" \
-H "Authorization: Bearer test" \
-H "Content-Type: application/json" \
-d '{"email":"debug@test.example","tenantSubdomain":"test"}'
# 3. Valid secret (expect 200)
curl -sS -X POST "https://CRM_HOST/auth/provision/resolve" \
-H "Authorization: Bearer YOUR_SECRET" \
-H "Content-Type: application/json" \
-d '{"email":"debug@test.example","tenantSubdomain":"test"}'
| HTTP | Meaning |
|---|---|
404 on /auth/provision/* | Wrong host, old image, or proxy path |
401 | Wrong PROVISIONING_SECRET |
400 on resolve | Workspace conflict, multi-workspace user, orphan user |
400 on move-user | Wrong membership state, only member/admin, inactive target workspace |
Never expose provisioning secret or workspace API keys in browser JavaScript.
Error handling checklist
404on provision routes → hostname/deploy, not secret401→ secret mismatch400onresolve→ tenant/user conflict404onlogin-token→ unknownworkspaceId400onlogin-token→ inactive workspace, or invalidpagepath (embedded mode)- User does not have permission when changing color theme → role lacks Workspace settings permission, or server build missing
themePresetin workspace field permissions; grant Workspace on the role or upgrade server
Code map
| Area | Path |
|---|---|
| Provisioning | aventora-crm/.../provisioning/ |
| Sales intelligence | aventora-crm/.../sales-intelligence/ |
| Engage by Aventora | aventora-crm/.../aventora/ |
| Cockpit UI | aventora-crm/.../pages/sales-cockpit/ |
| TIPS sync | aventora-crm/.../modules/tips-sync/ |
| Workspace presets | aventora-crm/.../modules/workspace-presets/ |
| Preset CRM workflows | .../constants/aventora-preset-crm-workflows.constant.ts, .../appliers/preset-crm-workflows.applier.ts |
| Preset cockpit rules | .../constants/aventora-preset-cockpit-rules.constant.ts, .../appliers/preset-automation-rules.applier.ts |
| Reapply preset CLI | .../database/commands/reapply-workspace-industry-preset.command.ts |
| Workflow Aventora actions | .../workflow/workflow-executor/workflow-actions/aventora-engagement/ |
| Engagement execution | .../aventora/aventora-engagement-execution.service.ts |
| Color theme presets | aventora-crm/packages/twenty-front/src/modules/ui/theme/ (definitions, picker, apply effect); SettingsWorkspace.tsx → Color theme; core.workspace.themePreset on server |
| Laravel plugin | aventora-crm/laravel-plugin/ |
| Admin CRM SSO | aventora-admin/app/api/phone/account-settings/crm-sso/ |
| Demo provision | domain-chatbot/LLM_full/demo_invitations/crm_provision.py |
| Cockpit mappings UI | aventora-admin/components/phone/cockpit-action-mappings-editor.tsx |
Engineer code index (fork internals): aventora-crm/docs/aventora-customizations.md.
Changelog
| Date | Change |
|---|---|
| 2026-06-14 | move-user provisioning: POST /auth/provision/move-user moves a user’s workspace membership (access only; CRM data stays in source workspace). |
| 2026-06-13 | Laravel integration guide: new CRM_LARAVEL_INTEGRATION.md for custom Laravel clients (provisioning, SSO, navigation, avatar). |
| 2026-06-13 | SSO avatar: optional avatar on resolve, login-token, provision/user, and GET /crm/sso; CRM downloads the image and sets workspace member profile picture. |
| 2026-06-13 | SSO embedded page mode: optional page on login-token and GET /crm/sso; CRM hides nav and deep-links via returnToPath + aventoraEmbedded=1; documented available page paths and host submenu pattern. |
| 2026-06-09 | Preset Aventora workflows v1: six DRAFT CRM workflow templates + two cockpit automation rules via industry presets; START_AVENTORA_ENGAGEMENT / _BULK workflow actions; workspace:reapply-industry-preset CLI; cockpit Enable / Auto-run UX; expanded UI paths and deploy checklist in this doc. |
| 2026-06-09 | Workspace color theme presets (themePreset): seven accent palettes; documented vs per-user Light/Dark/System (colorScheme); admin UI, workspace menu shortcut, API/SQL, permissions. |
| 2026-06-09 | Consolidated integration, Sales Cockpit, demo, upgrade, CLI, TIPS, and preset docs into this file. |
| 2026-06-08 | Sales Cockpit: Reconnect Recent / Oldies panels. |
| 2026-05-30 | Sales Cockpit action → Hub mappings via admin. |