@hanzo/hubspot
v1.0.0
Published
Hanzo AI for HubSpot — AI over your CRM records: summarize a contact/company/deal, draft an email, suggest next steps, and ask anything, with one-click write-back as a Note or Task. A CRM card UI extension plus a serverless backend that holds the Hanzo ke
Downloads
178
Readme
Hanzo AI for HubSpot
Brings Hanzo AI into a sales, marketing, or customer-success team's HubSpot CRM. Open the Hanzo AI tab on any contact, company, or deal record and Hanzo will:
- Summarize this record — read the record and its associated records (a contact's companies + deals, a deal's contacts + companies, …) and produce a tight, rep-ready overview.
- Draft email — generate a short, personalized follow-up grounded in the record data, ready to send.
- Next steps — a prioritized list of concrete actions for this record.
- Ask — freeform Q&A over the record and its context.
Every result can be written back to HubSpot with one click — logged as a
Note or created as a follow-up Task on the record, associated via the
platform's default engagement association. Model calls route through the same
api.hanzo.ai/v1 gateway as every other Hanzo surface, using the published
@hanzo/ai headless client.
This is a HubSpot public app on the developer-projects platform: a CRM card UI extension (React) backed by serverless functions. The Hanzo API key and the per-portal OAuth token live only in the serverless backend — they never reach the browser.
Architecture
Two credentials, kept apart by design:
- HubSpot — OAuth 2.0 authorization-code grant. The
client_secretlives only in the app's OAuth exchange (seesrc/oauth.tsfor the exact wire shape); the resulting per-portal access token is injected by HubSpot into the serverless runtime asprocess.env.PRIVATE_APP_ACCESS_TOKEN. The React card never sees it — it reads/writes CRM data only by calling the serverless functions. - Hanzo — an
hk-…API key stored as a HubSpot secret (HANZO_API_KEY), read by thehanzo-assistantserverless function and passed to@hanzo/ai. The browser only ever receives the final generated text.
CRM card (HanzoAssistant.tsx, React, no window/fetch)
│ runServerlessFunction('hanzo-assistant', { objectType, objectId, action, detail, model })
▼
app.functions/hanzo-assistant.js (holds HANZO_API_KEY; OAuth token from env)
├── @hubspot/api-client ── read record + associations (getById, associations.v4, batch.read)
├── @hanzo/hubspot (pure core) ── buildContextBlocks → assembleRecordContext (windowed)
└── @hanzo/ai createAiClient ── POST api.hanzo.ai/v1/chat/completions
◀── { text, truncated }
CRM card ── runServerlessFunction('hanzo-writeback', { kind, objectType, objectId, body })
▼
app.functions/hanzo-writeback.js
└── noteWriteBack / taskWriteBack (pure) → POST /crm/v3/objects/{notes,tasks}The pure core (src/config.ts, src/hanzo.ts, src/oauth.ts,
src/record.ts, src/api/*) has no HubSpot runtime and no DOM dependency — it
is the @hanzo/hubspot package the serverless functions import, and it is
covered by the vitest suite. The .tsx card and the .js serverless functions
are the two thin edges HubSpot's own tooling bundles and runs.
Layout
src/
config.ts Hanzo + HubSpot endpoints, object types, context budget
hanzo.ts the single model path (over @hanzo/ai) + context windowing + the 4 actions
oauth.ts HubSpot OAuth token-exchange request shaping
record.ts CRM record → ordered model-context blocks (property + association selection)
api/
hubspot-api.ts CRM v3 request shaping + response parsing (getObject, getAssociations, …)
write-back.ts Note / Task engagement payload assembly (default association edges)
index.ts the pure public surface (what the serverless functions import)
app/
public-app.json the public app: name, OAuth (scopes + redirect), card reference
extensions/
hanzo-assistant-card.json the CRM card config (crm.record.tab, objectTypes)
HanzoAssistant.tsx the React UI extension
app.functions/
serverless.json function → file + secrets map
hanzo-assistant.js reads record, runs the model (holds HANZO_API_KEY)
hanzo-writeback.js creates the Note / Task engagement
hsproject.json project name, srcDir, platformVersion (2025.1)
assets/ app-listing icons (16/32/128)Setup
1. Create a HubSpot developer account + app
Sign up for a free HubSpot developer account at https://developers.hubspot.com/ and create a test account (sandbox) to install into.
Install the HubSpot CLI and authenticate:
npm i -g @hubspot/cli hs init # writes hubspot.config.yml, authenticates an account hs account list # confirm your test account is connected
2. Configure OAuth
The app declares OAuth in src/app/public-app.json:
Redirect URL —
https://hubspot.hanzo.ai/oauth/callback. Change this to your own HTTPS callback that runs the token exchange (src/oauth.tstokenExchange→parseTokenResponse).Required scopes — read + write on the three object types the card mounts on:
crm.objects.contacts.read crm.objects.contacts.write crm.objects.companies.read crm.objects.companies.write crm.objects.deals.read crm.objects.deals.writeRead powers Summarize / Draft / Next steps / Ask; write powers the Note / Task write-back.
The install flow: send the user to authorizeUrl(...) (built in src/oauth.ts),
HubSpot redirects back to your callback with ?code=…&state=…, you POST the
tokenExchange request to https://api.hubapi.com/oauth/v1/token, and store the
returned token set. HubSpot then makes the per-portal token available to the
serverless functions.
3. Set the Hanzo key as a secret
The model key never goes in source or in the browser — it is a HubSpot secret:
hs secret add HANZO_API_KEY # paste your hk-… key from hanzo.idserverless.json declares HANZO_API_KEY on the hanzo-assistant function, so
HubSpot injects it as process.env.HANZO_API_KEY at runtime.
4. Upload + deploy
cd packages/hubspot
hs project upload # builds and uploads the project (creates a build)
hs project deploy # deploys the latest build to make the app live
# iterate with a live reload while developing:
hs project devhs project dev runs the card locally against your test account (records show a
"Developing locally" tag). To point hubspot.fetch/serverless calls at a local
backend during development, use a local.json proxy.
5. Install the card on record pages
- Install the app into your test account from the app's install URL (the OAuth consent flow above).
- Open any contact, company, or deal record.
- The Hanzo AI card appears as a tab (
crm.record.tab). Add it to the record's default view via Customize record if it isn't shown.
6. Use it
Pick a model (the picker is populated from /v1/models via the serverless
proxy; defaults to zen5), choose an action, and run. For Draft email
and Ask, type the topic / question first. Review the output, then
Save as Note or Create Task to write it back to the record.
App Marketplace
To list publicly on the HubSpot App Marketplace:
- Complete the app's listing in the developer portal (name, description, the
icons in
assets/, screenshots, support info). - Ensure the app requests only the scopes it uses (the six above) and has a production OAuth redirect and install flow.
- Submit for review at
https://developers.hubspot.com/docs/guides/apps/marketplace/listing-your-app.
Marketplace apps must use
oauthauth (already the case here).
Development
pnpm --filter @hanzo/hubspot test # vitest — the pure core
pnpm --filter @hanzo/hubspot typecheck # tsc --noEmit over src + tests
pnpm --filter @hanzo/hubspot build # emit dist/ (ESM + CJS) for the serverless importThe tests are pure (no network): CRM request shaping + parsing, OAuth
token-exchange shaping, record-context assembly/truncation, the @hanzo/ai
request shaping (over an injected client), and the Note/Task write-back payloads.
