> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vorel.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# CRM

> Per-driver setup: HubSpot, Salesforce, Zoho, Mindbody, Athenahealth, Tekmetric, Odoo, Toast, custom webhook, generic REST. OAuth + form-based connect; per-tenant field mapping; troubleshooting.

This page covers **integration-side setup** for each CRM driver. The platform-level overview
(driver interface, encryption, audit posture) lives at [Product → CRM](/product/crm); this page
is the per-driver how-to.

<Note>
  **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](/product/crm) for the architectural commitment and
  [Security → Data retention](/security/data-retention) for the full per-class TTL windows.
</Note>

## Driver maturity

Vorel ships **ten driver implementations** plus a registered-but-unimplemented `opentable` 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                                |

A few driver-specific notes:

* **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 `patientcase` surface.
* **Toast** wires the Toast loyalty `guest` object only (no notes, deals, or activities); writes
  outside `guest` are 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 a `deleteIsHard` flag. 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_webhook` driver 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 the `client_credentials` grant:
there's no user-redirect step; the operator enters the client id/secret in the admin form like a
form-based provider.)

<Steps>
  <Step title="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.
  </Step>

  <Step title="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).
  </Step>

  <Step title="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.
  </Step>

  <Step title="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'`.
  </Step>

  <Step title="Live test connection">
    Your operator clicks **Test connection**. Vorel calls the provider's lightest GET endpoint
    (`/v1/account` for HubSpot, `/services/data` for Salesforce, etc.) and reports success or the
    failure reason.
  </Step>
</Steps>

### Refresh handling

Each OAuth driver knows how to handle `401 Unauthorized` from the provider:

1. **Detect 401** during a tool call.
2. **Refresh** via the refresh token.
3. **Retry the original call** with the new access token (single retry).
4. **Re-encrypt + persist** the refreshed credential payload back into `tenant_credentials`
   (rotated\_at updated, audit-log entry written).
5. **Bubble up** if the refresh itself fails (audit-log + `auth_error` to the agent).

This means tokens stay fresh across process restarts without operator intervention. The agent
falls back gracefully on terminal failures (audit-log + "I'll have someone follow up" copy).

### Common OAuth failures

<AccordionGroup>
  <Accordion icon="lock" title="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.
  </Accordion>

  <Accordion icon="user-slash" title="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.
  </Accordion>

  <Accordion icon="key" title="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.
  </Accordion>

  <Accordion icon="cloud" title="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.
  </Accordion>
</AccordionGroup>

## Form-based providers (Mindbody / Tekmetric / Odoo)

Providers without standard OAuth use form-based credential entry:

<Steps>
  <Step title="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.
  </Step>

  <Step title="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).
  </Step>

  <Step title="Test connection live">
    Vorel calls the provider's lightest endpoint to verify. If it fails, the operator troubleshoots
    with the customer on the call.
  </Step>

  <Step title="Stamp active">
    On successful test, `tenant_credentials.status='active'`. The agent now uses the driver on every
    relevant tool call.
  </Step>
</Steps>

Form-based credentials don't auto-refresh (most are long-lived static API keys). Rotation is
operator-driven: customer rotates the API key at the provider, sends Vorel the new value, your
operator updates the form.

## Custom webhook driver

For CRMs without a Vorel-shipped driver, the `custom_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.

This is the path for any of:

* **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)

Setup:

<Steps>
  <Step title="Build the webhook receiver">
    Standard HTTPS endpoint accepting POST + JSON. Verify HMAC against the per-tenant secret
    Vorel surfaces during connection.
  </Step>

  <Step title="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.
  </Step>

  <Step title="Connect in admin">
    Operator enters the receiver URL + receives the HMAC secret. Test connection fires a
    no-op event your handler should ack with 200.
  </Step>
</Steps>

## Per-tenant field mapping

Every driver supports per-tenant field mapping via `tenant_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:

1. **Vorel-side scrub** runs via `/api/tenant/forget` (operator-gated, dry-run-by-default).
2. **CRM-side scrub** calls the driver's `deleteRecord` for the customer's CRM-side record.
3. **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.
4. **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.

Every credential rotation lands in `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_webhook` for 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](/product/crm): driver interface, credential encryption, audit posture
* [Quickstart](/getting-started/quickstart): Step 5 of the kickoff flow
* [Verticals](/verticals/clinic): vertical-specific CRM expectations (clinic uses Athenahealth,
  salon uses Mindbody, auto-service uses Tekmetric)
* [Security overview](/security/overview): encryption + audit posture

{/* verified-against: apps/web/src/lib/crm/index.ts instantiateDriver switch (10 driver implementations + opentable throw) */}

{/* verified-against: apps/web/src/lib/crm/hubspot-oauth.ts + salesforce-oauth.ts + zoho-oauth.ts + athenahealth.ts (OAuth refresh + re-encrypt path) */}

{/* verified-against: apps/web/prisma/schema.prisma TenantCrmFieldMapping model */}
