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

# Security

> Trust boundary, OAuth scope rationale, signature verification, and data handling.

Every request crossing the trust boundary is signed, every secret is
stored encrypted at rest, and every scope is the minimum needed to
make a feature work.

<Frame>
  <img src="https://mintcdn.com/cherttechnologiesinc/7NuyCzLl8sIsJEKo/hubspot/diagrams/trust-boundary.svg?fit=max&auto=format&n=7NuyCzLl8sIsJEKo&q=85&s=e1dc1ad4316dcad4cd569baa462ff00a" alt="HubSpot trust boundary" width="880" height="360" data-path="hubspot/diagrams/trust-boundary.svg" />
</Frame>

## Trust Boundary

| Side            | What it controls                                                                                                       | What it cannot do                                                                                                         |
| --------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| HubSpot account | Approve or revoke the OAuth grant; configure the workflow action; uninstall the app.                                   | Read or modify another connected account's data; impersonate Chert; bypass the signature requirement on inbound webhooks. |
| Chert           | Use the granted scopes; refresh tokens; create Communication engagements on contacts the customer's app has access to. | Read deals, companies, tickets, custom objects, or any contact property outside the documented mapping.                   |

The boundary runs along the OAuth grant. Revoking the grant in
HubSpot — or disconnecting from the Chert console — is sufficient to
end Chert's access to the account.

## OAuth Scopes

Each scope is the least-privilege scope that makes its feature work.

| Scope                        | Used by                                                 | Why this scope and not a broader one                                                                                                        |
| ---------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `oauth`                      | Token exchange and refresh                              | HubSpot requires this scope on every public app that uses OAuth.                                                                            |
| `crm.objects.contacts.read`  | Sidebar card, workflow action, contact-creation webhook | Reads `phone`, `mobilephone`, `firstname`, `lastname`, and `company` on the targeted contact only. No bulk read of the contacts collection. |
| `crm.objects.contacts.write` | Reply push                                              | Creates Communication engagements and associates them to the contact. Chert never updates contact properties.                               |
| `crm.schemas.contacts.read`  | Sidebar card                                            | Resolves the contact schema in accounts with custom property layouts so reads work without per-account configuration.                       |
| `automation`                 | Workflow custom action                                  | Registers the custom action and accepts its callbacks. Chert does not read, modify, or trigger workflows it did not register.               |

Chert does not request scopes for deals, companies, tickets, custom
objects, files, marketing email, ads, or social.

## HubSpot Signature v3

Every webhook and every fetch from the sidebar UI extension carries
HubSpot's v3 signature. Chert verifies the signature before any
database work and before parsing the request body.

| Component         | Detail                                                                                                       |
| ----------------- | ------------------------------------------------------------------------------------------------------------ |
| Header            | `X-HubSpot-Signature-v3`, base64-encoded HMAC-SHA256.                                                        |
| Header            | `X-HubSpot-Request-Timestamp`, Unix milliseconds.                                                            |
| Skew bound        | Reject when the timestamp differs from server time by more than five minutes.                                |
| HMAC input (POST) | `${method}${decodedRequestUri}${rawBody}${timestamp}`                                                        |
| HMAC input (GET)  | `${method}${decodedRequestUri}${timestamp}` — the body is omitted entirely, not included as an empty string. |
| Key               | The app's client secret.                                                                                     |
| Comparison        | Constant-time.                                                                                               |

The request URI is URL-decoded before hashing and includes the full
scheme, host, path, and query string.

<Warning>
  The GET signature shape is a frequent source of integration bugs.
  HubSpot signs GETs without any body segment — a GET signature
  computed over `${method}${uri}${""}${timestamp}` will always
  disagree.
</Warning>

## OAuth State Cookie

The install flow uses a short-lived signed cookie to bind the OAuth
`state` parameter to the originating Chert project.

| Property    | Value                                                             |
| ----------- | ----------------------------------------------------------------- |
| Cookie name | `hubspot_oauth_state`                                             |
| TTL         | 10 minutes                                                        |
| HttpOnly    | Yes                                                               |
| Secure      | Yes in production                                                 |
| SameSite    | `lax`                                                             |
| Signature   | HMAC-SHA256 of the JSON payload, keyed by the app's client secret |

The callback rejects the request if the cookie is missing, the
signature does not match, the embedded `state` does not match the
URL `state`, or the timestamp is older than the TTL.

## Token Storage

| Secret                    | Where it lives                  | At rest                                    |
| ------------------------- | ------------------------------- | ------------------------------------------ |
| HubSpot client ID         | Chert environment variables     | Standard environment-variable storage      |
| HubSpot client secret     | Chert environment variables     | Standard environment-variable storage      |
| Per-account access token  | `hubspot_tenants.access_token`  | Encrypted at rest by the database provider |
| Per-account refresh token | `hubspot_tenants.refresh_token` | Encrypted at rest by the database provider |

Refresh tokens rotate on every refresh — the helper persists the new
value before returning. Stale refresh tokens are not retained.

The Chert console never exposes access or refresh tokens in any UI or
API response. The admin dashboard surfaces only the granted scopes,
the token expiry, and the per-tenant counters.

## Data Handling

| Data                                       | Read                                      | Stored                                                                                                     | Retention                                                                                      |
| ------------------------------------------ | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| Contact `phone` and `mobilephone`          | Yes                                       | On the resolved Chert lead row                                                                             | Same retention as other Chert lead data                                                        |
| Contact `firstname`, `lastname`, `company` | Yes, when auto-creating                   | On the lead row                                                                                            | Same retention as other Chert lead data                                                        |
| Outbound message text                      | Sent through Chert's pipeline             | On the conversation row, plus a 500-character preview on `hubspot_send_events.request_payload` for support | Conversation rows follow the project's retention policy; event log rows roll off after 90 days |
| Inbound reply text                         | Pushed to HubSpot as a Communication body | On the conversation row                                                                                    | Same as outbound                                                                               |
| HubSpot Communication ID                   | Returned by the create call               | Implicit in the engagement on the contact timeline                                                         | Lives in HubSpot                                                                               |

Chert does not log full request or response bodies for HubSpot calls
beyond the 500-character preview noted above. PII fields are
redacted in error logs by server-side sanitization.

## Sub-processors

| Sub-processor  | Purpose                                                                                                                          |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| HubSpot, Inc.  | Authoritative source for contact data and the destination for reply pushes.                                                      |
| Vercel, Inc.   | Hosts the Chert console and API.                                                                                                 |
| Supabase, Inc. | Hosts the encrypted database that stores tenant rows, leads, conversations, and the event log.                                   |
| OpenAI, L.L.C. | Optional AI-drafting of messages when the account has the feature enabled. The HubSpot integration does not require AI drafting. |

The current sub-processor list is published at
`https://trychert.com/legal/sub-processors`.

## Incident Response

| Event                 | Action                                                                                                                                                                                                                   |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Suspected token leak  | Disconnect the affected HubSpot account from the Chert console, then re-install. The disconnect flips the tenant inactive and invalidates the cached tokens; the re-install rotates them.                                |
| Lost client secret    | Rotate the secret in HubSpot's developer dashboard. Chert's environment is updated, then every connected account refreshes its tokens on the next call. Existing access tokens remain valid until they expire naturally. |
| Stuck circuit breaker | The per-tenant `inbound_consecutive_failures` counter resets on the next successful reply push. To force a reset without waiting for a reply, contact Chert support.                                                     |

## See Also

* [Install](/hubspot/install) — scopes and consent flow.
* [Architecture](/hubspot/architecture) — how the verified payload flows through the runtime.
* [Limits](/hubspot/limits) — rate limits and retry semantics.
