Vorel writes into your CRM and treats your CRM as the system of record. Vorel-side persistence
for conversation transcripts, leads, customer profiles, appointments, and cases is short-lived and
ADR-locked: class-(c) rows purge on a 7–90 day post-CRM-write window. The authoritative copy
lives in your CRM. See Product → CRM for the architectural commitment and
Security → Data retention for the full per-class TTL windows.
Driver maturity
Vorel ships ten driver implementations plus a registered-but-unimplementedopentable slug.
Every shipped driver implements the full CrmDriver interface (read, search, create, update,
delete, introspection) and has unit coverage. HubSpot is the only driver with live production
traffic (proven on the Skyline tenant). Treat the rest as built-and-tested but not yet exercised
against a real account, and validate on the kickoff call before relying on a given provider.
Driver compatibility matrix
| Driver | Auth | Refreshable | Read | Write | Delete |
|---|---|---|---|---|---|
| HubSpot | OAuth 2.0 (auth-code) | Yes (auto-refresh + re-encrypt) | Yes | Yes | Soft (archive) |
| Salesforce | OAuth 2.0 (auth-code) | Yes | Yes | Yes | Soft (archive) |
| Zoho | OAuth 2.0 (per-DC) | Yes | Yes | Yes | Soft (archive) |
| Athenahealth | OAuth 2.0 (client_credentials) | Yes (lazy cache) | Yes | Yes (PHI-gated) | Unsupported (clinical retention) |
| Mindbody | Form (site id + API key) | n/a (long-lived) | Yes | Yes | Unsupported (manual at provider) |
| Tekmetric | Form (shop id + client id/secret) | n/a | Yes | Yes (appointments) | Unsupported (manual at provider) |
| Odoo | Form (URL + DB + user + key) | n/a | Yes | Yes | Hard (unlink) |
| Toast | Partner client-credentials | n/a | Yes (loyalty guest) | Yes (loyalty guest only) | Soft-redact (no hard delete) |
| Custom webhook | Bearer secret (HMAC) | n/a | n/a (write bridge) | Yes | Pass-through |
| Generic REST | API key / bearer / basic / none | n/a | Yes | Yes | Configurable (hard or unsupported) |
| OpenTable | partnership-only | n/a | n/a | n/a | n/a |
- Athenahealth is PHI-gated. No production tenant may connect until a signed HIPAA BAA and
compliance review exist. Write-back uses the least-PHI-invasive
patientcasesurface. - Toast wires the Toast loyalty
guestobject only (no notes, deals, or activities); writes outsideguestare rejected, and erasure is a soft-redact rather than a hard delete. - Generic REST (
rest_api) is a first-class configurable HTTP connector: you supply per-verb request templates, an auth type, and adeleteIsHardflag. It is the structured alternative to the fixed-envelope custom webhook. - OpenTable is reserved as a registered slug but not implemented. There’s no public OpenTable
API that would support agent-driven reads/writes, so tenants requesting OpenTable use the
custom_webhookdriver against a partner-built adapter.
OAuth providers (HubSpot / Salesforce / Zoho)
HubSpot, Salesforce, and Zoho connect via the shared OAuth authorization-code flow during the kickoff call. (Athenahealth and Toast also use OAuth, but via theclient_credentials grant:
there’s no user-redirect step; the operator enters the client id/secret in the admin form like a
form-based provider.)
Operator initiates the OAuth flow
From
/admin/tenants/[id]/crm, your Vorel operator clicks Connect HubSpot (or Salesforce /
Zoho). The provider’s OAuth page opens.You sign in on the provider side
Authenticate on HubSpot.com / Salesforce.com / etc. with an account that has the required scopes
(read + write on the relevant objects: leads, contacts, deals, appointments, depending on the
provider).
Provider redirects with a code
Provider sends back to Vorel’s callback handler with an authorization code. Vorel exchanges it
for an access token + refresh token + per-instance metadata.
Tokens land encrypted
Vorel envelope-encrypts the credential payload (access token + refresh token + instance URL +
scopes) with AES-256-GCM and writes the row into
tenant_credentials with status 'active'.Refresh handling
Each OAuth driver knows how to handle401 Unauthorized from the provider:
- Detect 401 during a tool call.
- Refresh via the refresh token.
- Retry the original call with the new access token (single retry).
- Re-encrypt + persist the refreshed credential payload back into
tenant_credentials(rotated_at updated, audit-log entry written). - Bubble up if the refresh itself fails (audit-log +
auth_errorto the agent).
Common OAuth failures
Refresh token expired (long-stale tenant)
Refresh token expired (long-stale tenant)
HubSpot refresh tokens expire after 6 months of inactivity. If a tenant’s CRM is dormant for
that long and a tool call eventually fires, refresh fails with
auth_error / refresh_failed.
Operator re-runs the OAuth flow; new tokens land.OAuth-grant user revoked at provider
OAuth-grant user revoked at provider
The user who connected may have been deactivated. Symptom is the same as above. Solution:
operator re-runs OAuth with an active user.
Scope mismatch
Scope mismatch
If the OAuth grant didn’t include all the scopes Vorel needs (e.g. tenant accidentally chose a
read-only OAuth profile), the agent’s first write call fails with
auth_error / insufficient_scope. Operator re-runs OAuth with the right scope set.Per-DC routing (Zoho)
Per-DC routing (Zoho)
Zoho has US / EU / IN / CN data centres. The OAuth flow captures which DC the customer is on;
the driver pins all calls to that DC’s endpoints. If the customer migrates DCs (rare), re-run
OAuth.
Form-based providers (Mindbody / Tekmetric / Odoo)
Providers without standard OAuth use form-based credential entry:Collect credentials from the customer
Each provider’s credential set differs: - Mindbody: Site ID + API key (issued in the
Mindbody developer portal). - Tekmetric: Shop ID + API key (issued from Tekmetric admin). -
Odoo: Instance URL + database + username + API key (or password). The user must have access
to
res.partner + crm.lead models.Operator enters them in the admin form
/admin/tenants/[id]/crm → driver-specific form. Credentials encrypt at rest the same way OAuth
tokens do (AES-256-GCM, env-var master key).Test connection live
Vorel calls the provider’s lightest endpoint to verify. If it fails, the operator troubleshoots
with the customer on the call.
Custom webhook driver
For CRMs without a Vorel-shipped driver, thecustom_webhook driver is the universal adapter:
- Vorel POSTs lead / appointment / customer payloads to your team’s HTTPS endpoint.
- Vorel signs each request with a per-tenant HMAC.
- Your team translates the canonical Vorel shape to the underlying CRM’s native format.
- CRMs without a Vorel-native driver (Pipedrive, Insightly, Microsoft Dynamics, etc.)
- Internal warehouses (push leads into your own database / data lake)
- Multi-fan-out scenarios (write to two CRMs simultaneously: Vorel writes once, your webhook handler fans out)
Build the webhook receiver
Standard HTTPS endpoint accepting POST + JSON. Verify HMAC against the per-tenant secret
Vorel surfaces during connection.
Map the canonical shape to your CRM
Vorel calls the receiver with a uniform shape (
{object, fields, idempotency_key}). Your
handler translates fields into the target CRM’s native call.Per-tenant field mapping
Every driver supports per-tenant field mapping viatenant_crm_field_mappings. Your CRM’s “Mobile
Phone” custom field is not Vorel’s customer.phone; the mapping table translates Vorel slot
names (e.g. lead.budget_max) to your CRM’s field paths (e.g. Salesforce lead.UF_Budget_Max__c,
HubSpot deal.budget_aed).
Configured during the kickoff call. Default mappings ship per-vertical for common slots; tenants
override per-field as needed.
The mapping is write-direction today: Vorel writes named fields into your CRM. Reading
arbitrary CRM custom fields back into Vorel slots is a Phase A2 follow-up.
Right-to-erasure
When a customer requests deletion under PDPL Art. 17 / GDPR Art. 17:- Vorel-side scrub runs via
/api/tenant/forget(operator-gated, dry-run-by-default). - CRM-side scrub calls the driver’s
deleteRecordfor the customer’s CRM-side record. - Drivers without a delete API (Mindbody, Tekmetric, Athenahealth) throw
delete_unsupported. Toast soft-redacts (scrubs PII, marks inactive) rather than hard-deleting. In every case the operator gets a flag indicating manual action at the provider’s dashboard may be required. - Audit log records the divergence so you can demonstrate compliance even when the CRM side requires manual action.
Connection rotation
Rotating credentials per your security policy:- OAuth providers: re-run the OAuth flow. Old refresh token continues to work until the new connection is verified, then operator marks the old row revoked.
- Form-based providers: customer issues a new API key at the provider, sends it to your
operator, operator updates the form. Old credential row is set to
status='revoked'. - Custom webhook: operator regenerates the HMAC secret in the admin form. Customer’s receiver updates to the new secret in the same maintenance window.
audit_log with the actor, the previous credential id,
and the new credential id.
What’s NOT supported today
- Multiple drivers per tenant. One CRM per tenant. Use
custom_webhookfor multi-fan-out. - Reading CRM custom fields back into Vorel slots. Write-direction only today.
- CRM-side webhook → Vorel. Listening for “CRM record updated” events is roadmap.
- Self-service CRM connect by tenant team. Operator-driven only; protects against misconfiguration during a regulated-vertical onboarding.
Related docs
- Product → CRM: driver interface, credential encryption, audit posture
- Quickstart: Step 5 of the kickoff flow
- Verticals: vertical-specific CRM expectations (clinic uses Athenahealth, salon uses Mindbody, auto-service uses Tekmetric)
- Security overview: encryption + audit posture