Aventora CRM — complete guide
Single reference for aventora-crm (Aventora CRM): deployment, upgrade, provisioning/SSO, industry presets, workspace color themes, Sales Cockpit, TIPS sync, and troubleshooting.
Audience: operators, integrators, client backend developers, sales ops — not the CRM end-user training path.
| Document | Who it is for |
|---|---|
| CRM User Guide | End users: people, pipeline, cockpit, campaigns, Engage, calendar booking |
| CRM video tutorials | End users: short walkthrough videos for the same daily work |
| This complete guide | Administrators and operators: deployment, environment, provisioning, CLI |
| Laravel Integration | Partner/host technical integration |
| Gmail IMAP Setup | Mailbox connection for administrators |
Laravel custom integration (no plugin): Laravel Integration — provisioning, SSO, embedded navigation, avatar.
Related platform docs: API Security Model, Platform API Keys, CRM MCP, Engagement Hub MCP, Outbound Email Templates, Gmail IMAP Setup
Table of contents
- Concepts
- Environment variables
- Deployment and upgrade
- Admin CLI commands
- Provisioning and SSO
- Embedded page mode (host-driven navigation)
- Laravel (custom integration): Laravel Integration
- Workspaces, users, and industry presets
- Sales Cockpit
- Campaign landing pages
- Demo workspace provisioning
- TIPS Services sync
- Laravel plugin
- Contacts and workspace API keys
- MCP (Cursor / Claude)
- Webhooks
- Troubleshooting
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) |
DEFAULT_PHONE_COUNTRY_CODE | Optional | ISO 3166-1 alpha-2 (e.g. US) used for new/empty phone inputs when workspace Settings → Data Model → Phones → Default Country Code is unset. Calling code is derived via libphonenumber. Unset keeps prior behavior (no default). |
SHOULD_PREFILL_STANDARD_OBJECTS | Optional | Default false. When true, workspace activation seeds Twenty demo CRM records (sample companies, people, opportunities, Quick Lead workflow, My First Dashboard). Leave unset/false for empty provisioned workspaces. When unset/false, Docker startup also runs workspace:remove-standard-objects-prefill to soft-delete those fixed seed UUIDs from existing workspaces. |
WHITE_LABEL | Optional | Partner display name for UI labels, API toasts/errors, and transactional emails. Unset or blank → Aventora (product name {name} CRM). Example: WHITE_LABEL=Acme → “Acme” / “Acme CRM”. Logos and website URLs are unchanged. Pass through Docker Compose from .env. Existing workspaces keep previously seeded role/workflow/tab names in the DB until recreated or manually renamed. |
USE_2FA | Optional | Default true. When true, workspace admins (and platform admins) must enable and enter 2FA at login. When false, CRM does not enforce admin 2FA enrollment or verification (workspace Settings → Security 2FA toggle is also ignored at login). Compose passes this into server/worker (${USE_2FA:-true}). |
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_SYSTEM_ACTOR_NAME | empty | Display name for system-created person createdBy. When empty, uses WHITE_LABEL (or Aventora). |
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.
Host-controlled Compose env files (.env next to docker-compose.yml) must stay off Git. On the Linux VPS restrict the file to the deployment user, for example sudo chmod 600 .env. Typical documented path: /opt/aventora/crm/.env.
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)
Production images are built by GitHub Actions for linux/amd64 and published to GitHub Container Registry as ghcr.io/<github-owner>/aventora-crm. latest is the most recent successful main build. Prefer the immutable sha-<commit-sha> tag for a specific deploy or rollback. Locally built images (especially on ARM machines) are not authoritative for production.
Containers run migrations and upgrade on startup 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 workspace:ensure-aventora-user-role-permissions
yarn command:prod workspace:backfill-provisioned-workspace-admins
yarn command:prod workspace:remove-standard-objects-prefill
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 → workspace:remove-standard-objects-prefill → 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; first listen can take 3–4 minutes — healthcheckstart_periodis 6 minutes)- Confirm
curl …/healthzand worker Up (notCreated) 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 are available to customers under agreement. Contact sales@aventora.ai.
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
All entries below are command names. In Docker/prod, run them as yarn command:prod <command-name>.
| 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:remove-standard-objects-prefill | Soft-delete Twenty demo seed rows (fixed UUIDs only) from active/suspended workspaces; skipped when SHOULD_PREFILL_STANDARD_OBJECTS=true unless --force. Auto on Docker startup. Use --destroy for permanent removal. |
workspace:seed-aventora-sales-cockpit-demo | Demo signals on existing people |
workspace:export-industry-preset | Export a catalog preset as partner-editable JSON (optional --format csv fields sheet). --all --out-dir dumps every industry default profile |
workspace:apply-industry-preset | Apply a catalog preset, or a partner JSON file (--from-file), to one workspace or every workspace |
workspace:reapply-industry-preset | Re-apply preset from each workspace’s stored industryPreset / industryProfile (catalog ids only) |
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 |
workspace:backfill-provisioned-workspace-admins | Promote the Laravel/TIPS workspace owner to Admin (auto on Docker startup) |
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>. Mostly additive (missing [preset:…] workflows, rules, fields). Engage defaults (AVENTORA_DEFAULT_CHANNEL / TYPE / INSTRUCTION) are overwritten from the preset.
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
Export a catalog preset for partners (JSON is the apply format; CSV is a review sheet only):
docker compose exec server yarn command:prod workspace:export-industry-preset \
--industry-preset insurance \
--industry-profile personal_lines \
--out /tmp/insurance-personal-lines.json
docker compose exec server yarn command:prod workspace:export-industry-preset \
--industry-preset generic \
--out /tmp/generic.json \
--format csv
--all --out-dir /tmp/presets writes one JSON per catalog industry using that industry’s default profile. --format csv also writes a .fields.csv beside each JSON (object,group,name,label,type,required). CSV cannot be applied.
Partners edit preset.industry (for example acme_dental), labels, fields, pipelines, workflows, and Engage defaults, then send the JSON back.
Apply a partner JSON file:
docker compose exec server yarn command:prod workspace:apply-industry-preset \
--from-file /tmp/acme.json -w <workspace-uuid> --force
docker compose exec server yarn command:prod workspace:apply-industry-preset \
--from-file /tmp/acme.json --force --all-workspaces
--from-file cannot be combined with --industry-preset / --industry-profile. Targeting every workspace requires both --force and --all-workspaces. File presets do not appear in onboarding or POST /auth/provision/*. If a workspace stores a custom industryPreset id that is not in the catalog, workspace:reapply-industry-preset fails and operators must re-apply with --from-file.
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; optional customSettings. Returns twentyApiKey when a new workspace is created |
POST | /auth/provision/rotate-api-key | Revoke Partner Provisioning keys and mint a new Admin JWT (twentyApiKey) |
POST | /auth/provision/login-token | SSO token + workspaceUrl + workspaceOrigin; optional page, avatar, customSettings |
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
All provisioning calls must run on the backend. The browser receives loginToken, expiresAt, workspaceOrigin, and workspaceUrl — never the provisioning secret.
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",
"customSettings": {
"engagementInitiatorPhone": "6473710396"
}
}
tenantSubdomain optional — omit for personal workspace mode.
Optional avatar — same rules as login-token.
Optional customSettings — per-user CRM settings bag; see Custom settings (customSettings).
Response includes wasCreated: { workspace, user }. When wasCreated.workspace is true, the response also includes twentyApiKey — a long-lived Admin-role workspace API key (shown once). Store it server-side and use it for later /rest/* and /mcp calls on https://{subdomain}.{crm-host}. Repeat resolve for an existing workspace omits twentyApiKey.
{
"workspaceId": "...",
"subdomain": "acme",
"userId": "...",
"wasCreated": { "workspace": true, "user": true },
"twentyApiKey": "<jwt>"
}
Never return twentyApiKey to the browser. If the key was not stored, call POST /auth/provision/rotate-api-key (same PROVISIONING_SECRET as resolve). That invalidates existing Partner Provisioning keys and returns a new twentyApiKey (shown once). Do not use link-workspace or demo-tenant for this — those mint Hub/demo keys with different names.
Rotate partner API key
POST /auth/provision/rotate-api-key
{
"workspaceId": "<uuid>",
"workspaceSubdomain": "acme"
}
One of workspaceId or workspaceSubdomain is required. 404 if the workspace is missing; 400 if neither identifier is sent.
{
"workspaceId": "<uuid>",
"subdomain": "acme",
"twentyApiKey": "<jwt>",
"revokedKeyCount": 1
}
revokedKeyCount is 0 when the workspace never received a partner key (still mints a new one). Keys created in CRM Settings → API keys, plus Engagement Hub CRM Sync and Demo Phone Sync, are left intact. Store twentyApiKey server-side; never send it to the browser.
Login token
POST /auth/provision/login-token
{
"workspaceId": "...",
"email": "agent@example.com",
"firstName": "Alex",
"lastName": "Agent",
"page": "/objects/people",
"avatar": "https://cdn.example.com/avatars/user.png",
"customSettings": {
"engagementInitiatorPhone": "+15551234567"
}
}
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).
customSettings is optional — see Custom settings (customSettings). The top-level engagementInitiatorPhone field is still accepted on this endpoint for backward compatibility; when both are sent, the top-level value wins.
Creates user + membership if missing. Redirect the browser to workspaceUrl once (first open, or when the CRM session is gone).
The login token is identity (email + workspace), not a page ticket. The response includes workspaceOrigin so hosts can reuse the same loginToken until expiresAt by changing returnToPath or sending postMessage { type: 'aventora.crm.navigate', page }. After the first /verify, CRM session cookies keep the user in.
The returned workspaceUrl always includes aventoraSso=1 (CRM hides Log out for SSO sessions). When page is set, the URL also includes returnToPath. Pass optional embedded: true to add aventoraEmbedded=1 (hides left nav — use for iframe embeds only, not new-tab deep links).
Custom settings (customSettings)
Optional JSON object on user provisioning endpoints: resolve, login-token, user, and move-user. Integrators (Laravel, custom HTTP clients) can send per-user CRM values before CRM adds first-class API fields. Unknown keys are ignored (debug-logged server-side).
| Key | Type | CRM behavior |
|---|---|---|
engagementInitiatorPhone | string | Sets/overwrites userWorkspace.aventoraEngagementInitiatorPhone (North American 10-digit normalization). Omitted or empty leaves the stored value unchanged. Users can still edit Settings → Profile → Engagement callback number; a later partner provision with this key overwrites that value. |
Example:
{
"workspaceId": "...",
"email": "agent@example.com",
"customSettings": {
"engagementInitiatorPhone": "6473710396"
}
}
To sync a number change from the partner platform, POST /auth/provision/user with workspaceId, email, and customSettings.engagementInitiatorPhone. Use PROVISIONING_SECRET. Do not use the workspace API key.
If a partner sends customSettings.engagementInitiatorPhone on every SSO (openCrm / login-token), the partner number wins over a number the user typed in CRM Profile.
On login-token, engagementInitiatorPhone may still be sent as a top-level field (legacy). Prefer customSettings for new integrations. Same overwrite rules apply.
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 extra SSO users; the workspace owner is Admin).
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).
Bulk SMS / calls: Person ownership is optional for CRM sync. Engagements, transcripts, and contact create/update still run via the workspace API key even when ownership lookup returns 404. Bulk rows often only carry user_phone_number (default broker phone) without a domain-chatbot user_id. Hub falls back to the Engagement Hub account email for ownership resolution when initiator email cannot be resolved from domain users. Phone-only lookup still requires userWorkspace.aventoraEngagementInitiatorPhone in CRM (set during SSO / login-token provisioning with engagementInitiatorPhone).
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.
Partner iframe embeds require CRM env FRAME_ANCESTORS (CSP allowlist). Staging leaves it unset so framing is denied. See Laravel Integration — Embedded navigation.
| Mode | Parameters | CRM behavior |
|---|---|---|
| Full CRM | No page | Normal CRM with left navigation and mobile bottom nav |
| Embedded page | page (+ embedded: true on direct API) | Left nav and mobile bottom nav hidden; user lands on the requested screen |
Laravel GET /crm/sso?page=... always sends embedded: true unless you pass embedded=0. Direct POST /auth/provision/login-token must include "embedded": true with page to hide nav (page alone only deep-links — used for Hub Call Log new-tab links that keep navigation).
In embedded mode:
- Log out is hidden in CRM (user exits via your app)
- Host menu owns section switches — mint
login-tokenonce, thenpostMessagenavigate or rewritereturnToPathon the same token untilexpiresAt - The user may still follow in-page links (e.g. open a record); CRM nav stays hidden for that session
- Do not call
login-tokenon every submenu click
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",
"embedded": true,
"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-...",
"workspaceOrigin": "https://{subdomain}.crm.example.com",
"workspaceId": "...",
"subdomain": "...",
"userId": "...",
"page": "/objects/people",
"embedded": true
}
| Field | Description |
|---|---|
url | Open this URL on first load (or when the iframe has no CRM session) |
loginToken | Reusable until expiresAt (also inside url) |
expiresAt | Token expiry (ISO timestamp). Mint again only after this. |
workspaceOrigin | CRM origin for postMessage and for rewriting returnToPath |
workspaceId | CRM workspace ID |
subdomain | Workspace subdomain |
userId | CRM user ID |
page | Echo of requested page (only when provided) |
embedded | Whether this session hides CRM chrome |
The official plugin caches the login token per user + workspace. A second GET /crm/sso with a different page before expiry does not call CRM login-token again; it rewrites returnToPath. Your frontend should cache the same way and prefer postMessage when the iframe is already logged in. This JSON does not include twentyApiKey. On first workspace create the plugin fires CrmWorkspaceProvisioned so the Laravel backend can persist the key. After a rotate, listen for CrmWorkspaceApiKeyRotated the same way.
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/)
In embedded mode (page + embedded: true / aventoraEmbedded=1), CRM hides all navigation including the settings sidebar. There is no /settings hub page — link each screen from your host app with its own page path (e.g. Profile, Accounts, Experience as separate menu items). See Laravel integration — Settings in embedded mode.
| 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
Call SSO once when the user opens CRM. Cache { loginToken, expiresAt, workspaceOrigin } until expiresAt.
| Your app menu | First open / token miss | Already logged in |
|---|---|---|
| CRM Home (full) | GET /crm/sso | in-session nav |
| Sales Cockpit | page=/cockpit | postMessage { type: 'aventora.crm.navigate', page: '/cockpit' } |
| People | page=/objects/people | postMessage to /objects/people |
| Companies | page=/objects/companies | postMessage to /objects/companies |
| Opportunities | page=/objects/opportunities | postMessage to /objects/opportunities |
| Profile | page=/settings/profile | postMessage to /settings/profile |
| Experience | page=/settings/experience | postMessage to /settings/experience |
| Connected accounts | page=/settings/accounts | postMessage to /settings/accounts |
If the iframe is not loaded, set iframe.src to {workspaceOrigin}{page} (session cookies). If that 401s or redirects to sign-in, open /verify with the cached loginToken and a new returnToPath. Mint a new login-token only after expiresAt.
Target origin for postMessage is workspaceOrigin. CRM ignores messages from origins not in FRAME_ANCESTORS, invalid page paths, and unknown types.
Embedded vs full CRM
| Feature | Without page | With page + embedded |
|---|---|---|
| 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 menu (postMessage / returnToPath) |
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 → no new
login-tokenwhile the token is valid → correct screen each time - Repeat
/verifywith the same token and a newreturnToPathwhile already logged in → route change, not logout/login
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)
Workspace owner vs extra users
On workspace create (resolve, provision/workspace, TIPS agent workspace), CRM assigns the standard Admin role to both AVENTORA_PLATFORM_ADMIN_EMAIL and the partner owner (adminEmail / TIPS agent). Extra users added later stay Aventora User (no Settings All Access). Existing tenants: workspace:backfill-provisioned-workspace-admins.
Admins must enable and use 2FA at login unless the CRM server sets USE_2FA=false.
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), Sales Cockpit automation rules, and Engage by Aventora defaults (AVENTORA_DEFAULT_CHANNEL, AVENTORA_DEFAULT_TYPE, AVENTORA_DEFAULT_INSTRUCTION). Apply/reapply overwrites those three workspace application variables so Custom Start Aventora matches the preset. Admins can still edit them afterward in Settings → Apps.
When a preset renames Person, Company, or Opportunity, the CRM also renames reciprocal relation field labels on linked records (for example, after Company becomes Employer, the Person detail field company shows Employer instead of Company). Only objects explicitly defined in the preset are updated; labels are not reset when a preset omits them.
Operators can export a built-in preset as JSON (workspace:export-industry-preset) for a partner to edit, then apply that file with workspace:apply-industry-preset --from-file. JSON is nested (field groups, pipeline stages, workflow steps, Engage defaults); CSV is a fields review sheet only. Partner files are ops CLI only — they do not appear in the signup catalog and are not accepted as industryPreset on POST /auth/provision/*. New workspaces still auto-apply catalog presets at activation.
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 |
|---|---|---|---|---|
| Aventora CRM Workflows | Workflows module | Settings → Workflows — names like Bulk SMS: Reconnect (internal [preset:…] key is hidden) | 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.
Step-by-step descriptions for workspace users — who is messaged, when the workflow runs, what the message is, and what to check before Activate — are in the CRM User Guide — Workflow templates. The tables below are the operator index (internal key, trigger, and a one-line summary).
CRM workflow templates (all industries)
| Key | UI name | Trigger | What it does |
|---|---|---|---|
av_wf_manual_engage_person | Engage Person (Aventora) | Manual, single person | Asks for channel and instruction, then starts a follow-up on that person. See Engage Person. |
av_wf_new_person_say_hello | New Person: Say Hello (SMS) | person.created | Hello SMS for every new person. See Say Hello. |
av_wf_new_person_qualify_call | New Person: Qualify (Call) | person.created | Qualification call for every new person. See Qualify (Call). |
av_wf_bulk_sms_checkin | Bulk SMS: Check Interest | Manual bulk people | Interest-check SMS for the people you select. See Check Interest. |
av_wf_bulk_reconnect | Bulk SMS: Reconnect | Manual bulk people | Reconnect SMS for the people you select. See Reconnect. |
av_wf_bulk_booking_link | Bulk SMS: Send Booking Link | Manual bulk people | Booking-link SMS for the people you select. See Send Booking Link. |
av_wf_happy_birthday | Happy Birthday | Daily CRON 0 14 * * * (14:00 UTC) | SMS when Date of Birth matches today’s month and day. See Happy Birthday. |
av_wf_holiday_new_year | Happy Holiday: New Year | Yearly CRON 0 14 1 1 * | January 1 SMS, up to 200 people. See New Year. |
av_wf_holiday_canada_day | Happy Holiday: Canada Day | Yearly CRON 0 14 1 7 * | July 1 SMS, up to 200 people. See Canada Day. |
av_wf_holiday_independence_day | Happy Holiday: Independence Day | Yearly CRON 0 14 4 7 * | July 4 SMS, up to 200 people. See Independence Day. |
av_wf_holiday_christmas | Happy Holiday: Christmas | Yearly CRON 0 14 25 12 * | December 25 SMS, up to 200 people. See Christmas. |
av_wf_manual_thank_you | Thank You After Meeting | Manual, single person | Thank-you SMS you start from one person. See Thank You After Meeting. |
Holiday templates are separate workflows. Activate only the ones that apply (Canada Day vs Independence Day). Shared industries do not seed Thanksgiving, Victoria Day, Family Day, or Easter because those dates move. Reable Real Estate seeds those four as manual DRAFT blasts (run from Workflows on the holiday) instead of crons.
Date of Birth: Standard Person has no birthdate. Insurance uses insDateOfBirth. Generic, real estate, mortgage, and financial advisor seed Date Of Birth (avDateOfBirth). Birthday lookup uses the first of those fields that exists. People without a date of birth are skipped.
Cron time is UTC. Admins can edit the schedule after seed. Holiday/birthday blasts stay DRAFT until activated because they can message many people. Query audiences skip do not contact and missing phone. If nobody matches, the step skips instead of failing the run.
Industry CRM workflow templates
| Industry | Key | UI name | Trigger | What it does |
|---|---|---|---|---|
| Insurance | av_wf_ins_renewal_reminder | Policy Renewal Reminder | Daily CRON 14:00 UTC | Texts the client when insRenewalDate is exactly 30, 15, 7, 3, or 0 days away (UTC). Admins change that list in Days before on the query step. See Policy Renewal Reminder. |
| Insurance | av_wf_ins_lapsed_policy | Lapsed Policy Outreach | policy.updated (insPolicyStatus) + FILTER LAPSED | Texts the client only when status becomes Lapsed. See Lapsed Policy Outreach. |
| Insurance | av_wf_ins_quote_presented | Quote Presented Follow-Up | opportunity.updated (insPolicyPipelineStage) + FILTER Quote Presented | Texts the quote contact only when the stage becomes Quote Presented. See Quote Presented Follow-Up. |
| Real estate | av_wf_re_follow_up_due | Follow-Up Due Today | Daily CRON 14:00 UTC | Texts people whose reNextFollowUpDate is today. See Follow-Up Due Today. |
| Real estate | av_wf_re_new_listing | New Listing Check-In | listing.created | Texts the contact linked on a new listing. See New Listing Check-In. |
| Real estate | av_wf_re_showing_scheduled | Showing Scheduled Follow-Up | opportunity.updated (reBuyerPipelineStage) + FILTER Showing Scheduled | Texts the deal contact only when the buyer stage becomes Showing Scheduled. See Showing Scheduled Follow-Up. |
| Mortgage | av_wf_mtg_closing_reminder | Closing Reminder | Daily CRON 14:00 UTC | Texts the borrower when mtgClosingDate is exactly 30, 15, 7, 3, or 0 days away (UTC). Admins change that list in Days before on the query step. See Closing Reminder. |
| Mortgage | av_wf_mtg_funded_thank_you | Funded Thank You | opportunity.updated (mtgMortgagePipelineStage) + FILTER Funded | Thank-you SMS only when the stage becomes Funded. See Funded Thank You. |
| Financial advisor | av_wf_fa_review_due | Review Due Today | Daily CRON 14:00 UTC | Booking-link SMS when faNextReviewDate is today. See Review Due Today. |
| Financial advisor | av_wf_fa_ask_referral | Ask For Referral | Manual, single person | SMS you start from one person, asking for an introduction. See Ask For Referral. |
| Reable Real Estate | av_wf_reable_portal_lead | Portal Lead: Website Form (SMS) | person.created + FILTER WEBSITE_FORM | Exact SMS when a person is created with Lead Source Website form. See Portal Lead. |
| Reable Real Estate | av_wf_reable_seller_valuation | Seller Valuation Request (SMS) | person.created + FILTER HOME_VALUATION | Exact SMS when a person is created with Lead Source Home valuation. See Seller Valuation. |
| Reable Real Estate | av_wf_reable_holiday_victoria_day | Happy Holiday: Victoria Day | Manual (global) | You run it on the holiday. Exact SMS, up to 200 people. See floating holidays. |
| Reable Real Estate | av_wf_reable_holiday_family_day | Happy Holiday: Family Day | Manual (global) | You run it on the holiday. Exact SMS, up to 200 people. See floating holidays. |
| Reable Real Estate | av_wf_reable_holiday_thanksgiving | Happy Holiday: Thanksgiving | Manual (global) | You run it on the holiday. Exact SMS, up to 200 people. See floating holidays. |
| Reable Real Estate | av_wf_reable_holiday_easter | Happy Holiday: Easter | Manual (global) | You run it on the holiday. Exact SMS, up to 200 people. See floating holidays. |
Generic industry gets the shared templates only. Reable also inherits the stock real-estate templates.
Workflow steps use custom action types START_AVENTORA_ENGAGEMENT (single person), START_AVENTORA_ENGAGEMENT_BULK (known people), and START_AVENTORA_ENGAGEMENT_QUERY (scheduled audience lookup). All call the shared AventoraEngagementExecutionService, which respects doNotContact on the person and uses workspace Aventora app variables + cockpit action mappings.
The workflow editor Cockpit action type control is a dropdown of executable cockpit actions (SEND_FOLLOW_UP, CALL_LEAD, …). That mapping selects Hub type (informational, conversational, appointment booking, …). Instruction is a multiline field prefilled from the selected action; you can edit it before the workflow runs.
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, daily/yearly CRON birthday and holiday blasts, cockpit auto-run) can contact leads without a manual click. Review every template before activation. People marked do not contact or missing the channel contact field (phone for SMS/call, email for email) are skipped. Date-based audiences also require that date. Holiday query audiences are capped at 200.
The full catalog is served at runtime by GET /auth/provision/industry-preset-catalog. Current values:
industryPreset | industryProfile values | Default profile | Profile required? |
|---|---|---|---|
real_estate | buyer_agent, listing_agent, team_leader, broker, general_realtor | general_realtor | Yes |
insurance | personal_lines, commercial_lines, benefits_advisor, broker_owner, general_insurance_advisor | general_insurance_advisor | Yes |
mortgage | mortgage_agent, mortgage_broker, commercial_mortgage, team_lead, broker_owner | mortgage_agent | Yes |
financial_advisor | financial_advisor, wealth_advisor, retirement_specialist, team_lead, practice_owner | financial_advisor | Yes |
generic (aliases: general, other) | default | default | No |
reable_real_estate (alias: reable-real-estate) | default | default | No |
Profile rules:
- For
real_estate,insurance,mortgage, andfinancial_advisor,industryProfileis required — a missing or unknown profile returns 400. - For
genericandreable_real_estate,industryProfileis optional and falls back todefault. - Unknown
industryPresetvalues return 400. The legacy onboarding idsgeneralandotherresolve togeneric.
Important: industryPreset / industryProfile are applied only when a workspace is created. When a call reuses or joins an existing workspace, the preset is ignored (the workspace keeps whatever preset it was created with). This applies to every endpoint that accepts the fields — resolve, workspace, and demo-tenant.
POST /auth/provision/resolve does accept industryPreset / industryProfile, but only takes effect when resolve creates a new workspace (new tenantSubdomain, or a new personal workspace when no subdomain is passed). A resolve that joins an existing subdomain — or returns a user's existing membership — gets no preset (generic CRM + standard Aventora fields). See Laravel Integration — resolve.
To control preset:
- Pass
industryPreset+industryProfileon the call that creates the workspace —POST /auth/provision/resolve(new subdomain/personal),POST /auth/provision/workspace, ordemo-tenant - Use onboarding UI (self-serve signup)
- Run
workspace:apply-industry-presetafter the fact (first time or to change preset/profile — required for existing workspaces, since preset is not re-applied on join) - Run
workspace:reapply-industry-presetafter upgrades to backfill new template artifacts on workspaces that already have a preset (also creates Listing / Policy custom objects when those industries are upgraded to preset v1.1.0+, Insurance client fields / marital-status options when upgraded to preset v1.2.0+, Date of Birth plus birthday/holiday/industry workflow templates on later versions, Reable Real Estate v1.1.0 portal/valuation and floating-holiday DRAFT workflows plus Home valuation lead source, and Engage defaults for channel / type / instruction) - For a partner-authored JSON file (not a catalog id), export with
workspace:export-industry-presetand apply withworkspace:apply-industry-preset --from-file. Custom ids cannot be re-applied withworkspace:reapply-industry-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 (classic indigo).
Available presets
themePreset value | Label | Accent palette |
|---|---|---|
classic | Classic | Indigo (classic 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"(default'classic') - Migration: applied automatically during normal CRM database upgrade
- 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 |
| Workflows nav shows 404 ("Off the beaten path") while other objects work | User's assigned role lacks Workflows settings permission (WORKFLOWS flag). Workflow objects require that flag even when canReadAllObjectRecords is true. Common on tenant workspaces (isPersonalWorkspace: false) when the user is still on Member instead of Aventora User. | Settings → Roles → Aventora User → enable Workflows, or reassign the user to Aventora User. On VPS: yarn command:prod workspace:ensure-aventora-user-role-permissions (also reassigns members on Hub-integrated workspaces), then cache:flush, log out and back in. Console may log [AventoraSSO] record-index-denied-read-access. |
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_*, TWILIO_ACCOUNT_SID / TWILIO_AUTH_TOKEN / TWILIO_SMS_NUMBER (same as Hub runtime) |
- 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.
Client Growth Hub uses this same POST /auth/provision/resolve path when an organization is created. The subdomain is the organization slug, except the reserved platform shell aventora, which is stored as aventora-cgh. Hub then creates the domain, account, and default Connect campaign. That campaign URL is the organization's Google ad landing page. Existing organizations are backfilled with the same call.
Partner workspace billing defaults (Hub): After domain + account creation, Hub applies pay-as-you-go + credit, 100 welcome credits (PARTNER_CRM_INITIAL_CREDITS or DEFAULT_INITIAL_CREDITS), and platform default engagement rates. Only runs while the account is still on the initial free plan from registration.
Manual batch (ops backfill)
When CRM workspaces already exist but domain-chatbot, Engagement Hub, and CRM↔Hub wiring are missing, your Aventora operator runs a provisioning backfill from the Hub deployment. That process:
- Lists CRM workspaces that are not yet linked to Hub.
- Creates the matching domain-chatbot tenant and Hub account for each workspace.
- Applies shared inbound/Twilio defaults and stores CRM link credentials.
- Optionally syncs workspace members into domain-chatbot and Hub when members were added before linking.
Contact your Aventora administrator or support if you need this backfill for an existing deployment.
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 |
TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN (or TWILIO_API_KEY_SID + TWILIO_API_SECRET), TWILIO_SMS_NUMBER / TWILIO_PHONE_NUMBER | Telephony stamped onto new Hub/domain accounts (same vars as Hub runtime). Optional PROVISION_* overrides still accepted |
TWILIO_MASTER_ACCOUNT_SID, TWILIO_MASTER_AUTH_TOKEN (or TWILIO_MASTER_API_KEY + TWILIO_MASTER_API_SECRET), TWILIO_WEBHOOK_BASE_URL | aventora-admin Add Domain auto-provision (search/buy number, create API key) |
TWILIO_PROVISION_TEST_MODE=true (domain-chatbot, test credentials only) | E2E auto-provision dry run: magic number search results, synthetic API key, purchase +15005550006 via Twilio test API (no real number charges). Disable before production provisioning. |
CRM_HUB_PROVISION_CALLBACK_SECRET | Authenticates CRM POST /internal/crm-hub-provision (same value as CRM AVENTORA_HUB_PROVISION_CALLBACK_SECRET) |
On AWS staging, Hub receives DOMAIN_CHATBOT_SUPER_ADMIN_TOKEN from the same Secrets Manager value as Chatbot PLATFORM_ADMIN_API_KEY (platform-wide domain API key). Username/password remain valid for VPS/Compose and bootstrap superadmin ensure-super-admin (authoritative CHATBOT_SUPER_ADMIN_* in platform/shared). Chatbot CRM-provision routes accept either a super-admin JWT or that platform-wide API key via Authorization: Bearer.
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 for new domains can be set during aventora-admin → Add Domain (optional telephony section: manual API key + number, or auto-provision from the platform master Twilio account). Classic Auth Token is only available post-provision in Account Settings. CRM workspace backfill (CLI / provision-from-crm-workspace) still accepts operator-supplied env or payload credentials including classic auth token.
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 Laravel Integration — 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 (deep-links; Laravel route also sends embedded to hide CRM nav):
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. GET /crm/sso never returns twentyApiKey (long-lived Admin key). Listen for Aventora\Crm\Events\CrmWorkspaceProvisioned on first workspace create to persist it server-side. If that key was not stored, call Crm::rotateWorkspaceApiKey() and listen for CrmWorkspaceApiKeyRotated.
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 first-time resolve (twentyApiKey, store once), POST /auth/provision/rotate-api-key, link-workspace, demo-tenant, or CRM Settings → API keys.
Bulk email / engagement (workspace API key)
POST /rest/aventora/start-engagement-bulk sends informational email (or phone/SMS) through Engagement Hub.
- Some contacts:
{ "personIds": ["…"], "channel": "email", "type": "informational", "emailSubject": "…", "emailBodyHtml": "<p>…</p>" }(max 1000 ids). - All contacts:
{ "audience": "all", "channel": "email", "type": "informational", "emailSubject": "…", "emailBodyHtml": "<p>…</p>" }. Do not sendpersonIdsorviewId. CRM skips do not contact and people without email, then chunks Hub submits at 1000. - Person view:
GET /rest/aventora/person-viewsthen{ "viewId": "…", "channel": "email", "type": "informational", "emailSubject": "…", "emailBodyHtml": "<p>…</p>" }. Mutually exclusive withpersonIdsand"audience": "all". CRM applies the view’s filters at send time.
Use a Hub template (emailTemplateId + emailTemplateParams) instead of emailBodyHtml when the layout is saved in Admin. htmlBody / html_body is accepted as the same inline HTML. Optional fromName / from_name sets the From display name; otherwise CRM uses Profile / default initiator CRM user name / workspace display name. See Outbound Email Templates.
MCP (Cursor / Claude)
Workspace API keys can also call POST /mcp (JSON-RPC 2.0). That endpoint is the Aventora CRM MCP server: named tools wrap /rest/people and /rest/notes. Copy the Cursor snippet from Settings → AI. Full tool list: CRM MCP.
Claude Custom Connectors use the same /mcp URL with OAuth (authorization-code + PKCE), not an API key. Operators register the confidential client with application:register-claude-mcp-client. See CRM MCP.
For outbound calls and SMS, use the Engagement Hub MCP with a Hub API key — not CRM MCP.
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
Changelog
| Date | Change |
| --- |
| 2026-10-05 | Insurance preset v1.5.0: Clients gain insFolderName and insSearchKey. Policies gain insProduct, insPremiumAmount, insDeductibleAmount, insExpiryDate, insPremiumNumeric, and insCoverageNumeric. Notes stay the Notes object. Existing insurance and TIPS workspaces pick these up via workspace:reapply-industry-preset. |
| 2026-10-05 | POST /auth/provision/reprovision-hub with connectUrlOnly: true reads the existing Connect campaign URL. It does not create another Hub account or domain. |
| 2026-10-04 | Client Growth Hub calls POST /auth/provision/resolve when an organization is created. CRM then provisions the Hub account and domain. The default Connect campaign is that organization's Google ad landing page. Existing organizations are backfilled with the same call. |
| 2026-09-29 | Renewal and closing reminders: av_wf_ins_renewal_reminder and av_wf_mtg_closing_reminder match exact day offsets (default 30, 15, 7, 3, 0) instead of a daily window. workspace:reapply-industry-preset updates a version only while its audience is still the original within-30 or within-7 search. A saved Days before list is left as-is. |
| 2026-09-29 | Workflow template descriptions: User-facing write-ups for every seeded CRM workflow (shared, insurance, real estate, mortgage, financial advisor, Reable) are in the CRM User Guide. This guide keeps the operator key index. |
| 2026-09-28 | Related-record spreadsheet import: Importing a record with a many-to-one person relation (Policy client, Listing contact, Opportunity point of contact, and the same link in every preset) matches the person by email, then phone. See Importing records. |
| 2026-09-24 | Partner bulk email billing: POST /rest/aventora/start-engagement-bulk with channel: "email" records an outbound-email usage row in Engagement Hub when each row is accepted. |
| 2026-09-21 | Partner bulk email From: Laravel htmlBody is accepted on POST /rest/aventora/start-engagement-bulk. From display name keeps the CRM user/Profile name (including API-key default initiator user account name), not Hub EMAIL_DISPLAY_NAME. |
| 2026-09-19 | Partner bulk email From: API-key POST /rest/aventora/start-engagement-bulk falls back to workspace display name and optional fromName / from_name. |
| 2026-09-17 | Partner preset JSON: workspace:export-industry-preset writes a catalog preset as JSON (optional --format csv fields sheet). workspace:apply-industry-preset --from-file applies a validated file to one workspace or, with --force --all-workspaces, every workspace. File presets are ops CLI only — not onboarding and not industryPreset on provision. Custom industry ids re-apply with --from-file, not workspace:reapply-industry-preset. |
| 2026-09-17 | Industry preset Engage defaults: Applying or re-applying a preset writes AVENTORA_DEFAULT_CHANNEL, AVENTORA_DEFAULT_TYPE, and AVENTORA_DEFAULT_INSTRUCTION (Custom Start Aventora). Existing workspaces pick this up via workspace:reapply-industry-preset. This overwrites those three Settings → Apps values. |
| 2026-09-17 | Compose healthcheck: Server /healthz has a 6-minute start_period so first-boot migrations do not mark the container unhealthy or leave the worker in Created. |
| 2026-09-14 | GHCR production images: GitHub Actions builds linux/amd64 CRM images and publishes them to GitHub Container Registry. Prefer sha-* tags for exact deploys; latest is the latest successful main build. |
| 2026-09-12 | Engagement callback overwrite: Partner customSettings.engagementInitiatorPhone on POST /auth/provision/user (and the shared helper on resolve, login-token, move-user) overwrites userWorkspace.aventoraEngagementInitiatorPhone. Omitted or empty does not clear. Use PROVISIONING_SECRET, not the workspace API key. |
| 2026-09-12 | Reusable SSO token: login-token is identity, not a page ticket. Response includes workspaceOrigin. Partners mint once, then postMessage navigate or rewrite returnToPath until expiresAt. Laravel plugin caches per user+workspace. /verify reuses a matching CRM session instead of clearing cookies. |
| 2026-09-09 | CRM video tutorials: Linked the 44 end-user walkthroughs at CRM video tutorials. |
| 2026-09-09 | Reable Real Estate v1.1.0: DRAFT Portal Lead / Seller Valuation workflows (person.created + Website form / Home valuation filters) and manual Victoria Day / Family Day / Thanksgiving / Easter blasts. Adds Home valuation lead source. Existing Reable workspaces pick these up via workspace:reapply-industry-preset. Does not ingest portal or valuation forms and does not compute floating holiday dates. |
| 2026-09-09 | Reable Real Estate preset: New industry reable_real_estate (alias reable-real-estate) extends stock real estate with extra lead sources, Mutual Release on buyer/seller, Pre-Con and Assignment Seller pipelines, and verbatim Engage packs. Apply with workspace:apply-industry-preset --industry-preset reable_real_estate --force. Profile default is optional. Stock real_estate tenants are unchanged. |
| 2026-09-07 | Docs audiences: End-user CRM work lives in the CRM User Guide. This complete guide remains the administrator, operator, and integrator reference. |
| 2026-08-28 | People spreadsheet import matching: Upsert identity is email, then phone. First + last name is used only when the imported row has neither. Duplicate emails in the file are not blocked. |
| 2026-08-23 | Bulk email by Person view: GET /rest/aventora/person-views lists Person views. POST /rest/aventora/start-engagement-bulk accepts viewId (mutually exclusive with personIds and "audience": "all"). See Outbound Email Templates. |
| 2026-08-23 | Rotate partner API key: POST /auth/provision/rotate-api-key revokes Partner Provisioning keys and returns a new twentyApiKey. Same PROVISIONING_SECRET as resolve. Laravel Crm::rotateWorkspaceApiKey() fires CrmWorkspaceApiKeyRotated. |
| 2026-08-23 | Query / Build using AI eligibility: SMS and phone require a phone number; email requires an email; date audiences require that date (birthday = Date of Birth, month and day). Missing data skips the person; empty audience skips the run. Applies to every query outreach and record-triggered send, not only birthday texts. Lookup no longer fails with Property "phones" was not found in "person". |
| 2026-08-23 | Host env file permissions: Restrict the Compose .env on the VPS (chmod 600; typical path /opt/aventora/crm/.env). Do not commit live secrets. |
| 2026-08-21 | Build using AI: Optional workflow-editor overlay (server env USE_AI, default off). Users describe automations in everyday language; the builder applies Aventora defaults (birthday texts use Date of Birth / BIRTHDAY_TODAY) and does not quiz on API names. Writes a draft on this workflow. Does not enable Twenty Ask AI (IS_AI_ENABLED stays off). |
| 2026-08-21 | Admin 2FA: Server env USE_2FA (default true) requires workspace admins to enable and enter 2FA at login. Set USE_2FA=false to skip that enforcement. Compose passes the flag into server/worker. |
| 2026-08-21 | Workspace owner is Admin: Laravel adminEmail and the TIPS workspace-owner agent get the standard Admin role (full settings). Extra SSO users stay Aventora User. Platform admin remains Admin. Backfill: workspace:backfill-provisioned-workspace-admins (also runs on Docker startup). |
| 2026-08-20 | Workflow command menu: Preset workflow labels no longer show the internal [preset:av_wf_…] key. Users see Bulk SMS: Reconnect, not [preset:av_wf_bulk_reconnect] Bulk SMS: Reconnect. |
| 2026-08-20 | CRM workflow templates: Birthday, four fixed-date holiday blasts, and Thank You After Meeting seed as DRAFT on every industry. Insurance, real estate, mortgage, and financial advisor also get industry-specific DRAFT workflows. Birthday needs Date Of Birth filled (insDateOfBirth or avDateOfBirth). Holiday audience is capped at 200 and skips do-not-contact / missing phone. Cron is 14:00 UTC; activate only the holidays that apply. Existing workspaces pick these up via workspace:reapply-industry-preset. |
| 2026-08-20 | Voice memos: New notes/tasks default to Voice Note / Voice Task plus the date and time. Custom titles are not overwritten. |
| 2026-08-20 | Voice-memo transcription: Docker Compose passes OPENAI_API_KEY into the CRM server/worker. When the key is unset, Notes/Tasks Transcribe is disabled. |
| 2026-08-20 | Engage by Aventora disabled reason: Quick/bulk toolbar actions stay visible when blocked. Hover and click explain missing callback number (Settings → Profile), missing AVENTORA_ENGAGEMENT permission, or Hub not connected. |
| 2026-08-20 | Bulk email to all contacts: POST /rest/aventora/start-engagement-bulk accepts "audience": "all" (mutually exclusive with personIds). See Outbound Email Templates. |
| 2026-08-20 | Partner workspace API key: POST /auth/provision/resolve returns twentyApiKey (Admin role) only when a new workspace is created. Store it server-side; Laravel GET /crm/sso omits it and fires CrmWorkspaceProvisioned. |
| 2026-08-18 | Claude Custom Connectors: CRM POST /mcp returns HTTP 200 (not Nest’s default 201) so Claude can read tool results. See CRM MCP. |
| 2026-08-18 | Claude Custom Connectors: CRM /mcp named tools run as the user who approved OAuth. See CRM MCP. |
| 2026-08-18 | Claude Custom Connectors: CRM /mcp accepts Application OAuth tokens (mcp:tools) in addition to workspace API keys. See CRM MCP. |
| 2026-08-18 | CRM MCP: POST /mcp is an Aventora-owned named-tool server (People/Notes). Cursor snippet is under Settings → AI. See CRM MCP. |
| 2026-08-18 | /crm URL: docs.aventora.ai/crm redirects to this complete guide so in-app Documentation links resolve. |
| 2026-08-18 | In-app help: CRM pages and panels include an info icon for English contextual help. Documentation nav opens docs.aventora.ai/crm; in-app /docs one-pager removed. |
| 2026-08-17 | Workflows — Aventora Engagement: Cockpit action type is a dropdown. Instruction is a multiline field prefilled from the selected action and can be edited. |
| 2026-08-17 | Insurance preset v1.2.0: Broader Client fields (kids counts, source/status/market, commercial/recruiting fields). Reapply adds missing SELECT options (Couple) and deactivates Dependents. People spreadsheet import maps Email 2 / Phone 2 and Note/Task columns. |
| 2026-08-11 | Partner iframe embeds: CRM FRAME_ANCESTORS env — unset = DENY (staging); set = CSP allowlist for partner host origins. |
| 2026-08-09 | Chatbot superadmin ensure: platform bootstrap uses superadmin ensure-super-admin so platform/shared CHATBOT_SUPER_ADMIN_PASSWORD creates or reconciles the DB hash (re-run bootstrap to recover drift). API-key / DOMAIN_CHATBOT_SUPER_ADMIN_TOKEN health does not prove password-login alignment. |
| 2026-08-09 | CRM Hub provision Twilio env: stamps telephony from Hub runtime TWILIO_ACCOUNT_SID / TWILIO_AUTH_TOKEN / TWILIO_SMS_NUMBER (or phone number / API key pair). Separate PROVISION_* Twilio vars are optional overrides only — admin Add Domain and CRM workspace auto-provision share the same credentials. |
| 2026-08-09 | AWS Hub CRM provision auth: staging Hub injects DOMAIN_CHATBOT_SUPER_ADMIN_TOKEN from the bootstrap-managed Chatbot PLATFORM_ADMIN_API_KEY (same Secrets Manager JSON key). Username/password remain a VPS/Compose / superadmin ensure-super-admin path; Chatbot super-admin routes accept JWT or platform-wide API key. |
| 2026-07-28 | White-label brand name: optional server env WHITE_LABEL (Docker .env → Compose) sets the partner display name for CRM UI labels, user-facing API messages, and transactional emails. Unset/blank keeps Aventora / Aventora CRM. Injected into the SPA via window._env_. Logos and aventora.ai URLs are unchanged. |
| 2026-07-22 | Workspace demo seed cleanup: Docker startup runs workspace:remove-standard-objects-prefill unless SHOULD_PREFILL_STANDARD_OBJECTS=true. Soft-deletes Twenty’s fixed demo UUIDs only (Airbnb/etc companies, people, opportunities, Quick Lead, My First Dashboard); does not touch user or integration data. Pass --destroy for permanent deletion. |
| 2026-07-22 | Workspace activation: demo CRM sample data (companies/people/opportunities/Quick Lead/dashboard) is no longer loaded by default. Opt in with server env SHOULD_PREFILL_STANDARD_OBJECTS=true. |
| 2026-07-19 | Contact record tabs: when Person has listings / policies relations (Real Estate / Insurance presets), the CRM shows dedicated Listings / Policies tabs as the second tab after Timeline, with an Add button to create related records. |
| 2026-07-19 | Industry presets reapply: workspace:reapply-industry-preset falls back to the industry default profile when a workspace has industryPreset but no industryProfile (common on older real_estate tenants), then persists that profile. |
| 2026-07-19 | Industry presets v1.1.0: Real Estate seeds a Listing custom object (Contact → Listings); Insurance renames Opportunity → Quote and seeds a Policy custom object (Client → Policies). New PresetCustomObjectsApplier; existing workspaces pick this up via workspace:reapply-industry-preset. |
| 2026-07-17 | Add Domain telephony: auto-purchased Twilio numbers now set inbound SmsUrl to {TWILIO_WEBHOOK_BASE_URL}/sms/webhook (plus existing VoiceUrl). Missing SmsUrl caused Hub to miss customer SMS replies while delivery status still worked. |
| 2026-07-14 | Embedded SSO: Laravel plugin again sends embedded: true whenever page is set (hides CRM left nav). Direct login-token still needs explicit "embedded": true; page alone only sets returnToPath (Hub Call Logs). |
| 2026-07-14 | Default phone country: optional server env DEFAULT_PHONE_COUNTRY_CODE (ISO, e.g. US) falls back when workspace Phones field Default Country Code is unset; exposed via client-config; workspace setting still overrides. |
| 2026-07-12 | Industry presets: replaced the truncated "example profiles" list with the full catalog (all industryProfile values, default profile, and which presets require a profile); documented general/other → generic aliases and 400 behavior. |
| 2026-07-12 | Industry presets: corrected stale note — POST /auth/provision/resolve does accept industryPreset / industryProfile; they apply only when resolve creates a new workspace (new subdomain or personal), matching Laravel Integration. |
| 2026-07-12 | Industry presets: applying or re-applying a preset now also renames reciprocal relation field labels when Person, Company, or Opportunity object labels change (for example person.company follows the Company singular label). |
| 2026-07-05 | Campaign landing pages: Hub /campaign/{token} uses TWENTY_BASE_URL / CRM_API_URL for CRM public config and submit. |
| 2026-07-05 | SSO deep links: login-token optional embedded: true adds aventoraEmbedded=1 (hide nav). page alone sets returnToPath only — use for new-tab person links from Engagement Hub Call Logs. |
| 2026-07-05 | Campaign landing pages: CRM Campaigns UI (/campaigns), Hub /campaign/{token} form, public submit API, tag-only vs auto-engage. See Campaign landing pages. |
| 2026-07-02 | Person ownership vs CRM sync: documented that bulk Hub outreach can sync contacts/engagements without ownership context; Hub uses account email fallback when domain-user email is missing. |
| 2026-07-02 | Add Domain telephony (test mode): domain-chatbot TWILIO_PROVISION_TEST_MODE=true enables end-to-end auto-provision with Twilio test credentials (magic number +15005550006, synthetic API key). Use live master credentials with this flag off for real purchases. |
| 2026-07-02 | Add Domain telephony: optional Twilio setup in aventora-admin (skip, manual API key, or auto-provision). provision-domain accepts API key auth only; classic auth token remains Account Settings only. |