Skip to main content
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.
HubSpot trust boundary

Trust Boundary

SideWhat it controlsWhat it cannot do
HubSpot accountApprove 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.
ChertUse 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.
ScopeUsed byWhy this scope and not a broader one
oauthToken exchange and refreshHubSpot requires this scope on every public app that uses OAuth.
crm.objects.contacts.readSidebar card, workflow action, contact-creation webhookReads phone, mobilephone, firstname, lastname, and company on the targeted contact only. No bulk read of the contacts collection.
crm.objects.contacts.writeReply pushCreates Communication engagements and associates them to the contact. Chert never updates contact properties.
crm.schemas.contacts.readSidebar cardResolves the contact schema in accounts with custom property layouts so reads work without per-account configuration.
automationWorkflow custom actionRegisters 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.
ComponentDetail
HeaderX-HubSpot-Signature-v3, base64-encoded HMAC-SHA256.
HeaderX-HubSpot-Request-Timestamp, Unix milliseconds.
Skew boundReject 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.
KeyThe app’s client secret.
ComparisonConstant-time.
The request URI is URL-decoded before hashing and includes the full scheme, host, path, and query string.
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.
The install flow uses a short-lived signed cookie to bind the OAuth state parameter to the originating Chert project.
PropertyValue
Cookie namehubspot_oauth_state
TTL10 minutes
HttpOnlyYes
SecureYes in production
SameSitelax
SignatureHMAC-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

SecretWhere it livesAt rest
HubSpot client IDChert environment variablesStandard environment-variable storage
HubSpot client secretChert environment variablesStandard environment-variable storage
Per-account access tokenhubspot_tenants.access_tokenEncrypted at rest by the database provider
Per-account refresh tokenhubspot_tenants.refresh_tokenEncrypted 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

DataReadStoredRetention
Contact phone and mobilephoneYesOn the resolved Chert lead rowSame retention as other Chert lead data
Contact firstname, lastname, companyYes, when auto-creatingOn the lead rowSame retention as other Chert lead data
Outbound message textSent through Chert’s pipelineOn the conversation row, plus a 500-character preview on hubspot_send_events.request_payload for supportConversation rows follow the project’s retention policy; event log rows roll off after 90 days
Inbound reply textPushed to HubSpot as a Communication bodyOn the conversation rowSame as outbound
HubSpot Communication IDReturned by the create callImplicit in the engagement on the contact timelineLives 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-processorPurpose
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

EventAction
Suspected token leakDisconnect 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 secretRotate 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 breakerThe 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 — scopes and consent flow.
  • Architecture — how the verified payload flows through the runtime.
  • Limits — rate limits and retry semantics.