HubSpot Integration
Purpose and Scope
The HubSpot integration connects a self-hosted Cal.diy instance to a HubSpot developer app so booking activity can be represented in HubSpot CRM. The setup is centered on OAuth: HubSpot issues a Client ID and Client Secret, Cal.diy stores those values in its environment, and HubSpot redirects users back to a Cal.diy callback route after authorization. This page is for operators configuring the integration and contributors looking for the package entrypoints that expose HubSpot behavior inside the app store package.
Sources: apps/docs/content/apps/hubspot.mdx, packages/app-store/hubspot/api/index.ts, packages/app-store/hubspot/index.ts
The official HubSpot setup flow is intentionally short, but each value must match the deployed Cal.diy instance. The Client ID identifies the HubSpot developer app, the Client Secret authenticates Cal.diy when exchanging OAuth information, and the redirect URL tells HubSpot where to return the browser after the user approves the app. If any of those three pieces are inconsistent, HubSpot authorization may complete on the HubSpot side but fail when Cal.diy receives the callback.
Relevant Source Files
apps/docs/content/apps/hubspot.mdx— the reader-facing setup guide for creating a HubSpot developer app, configuring OAuth credentials, selecting the required contact scope, and entering the callback URL.packages/app-store/hubspot/api/index.ts— the HubSpot app-store API barrel that exports theaddandcallbackAPI handlers used by the integration surface.packages/app-store/hubspot/index.ts— the top-level HubSpot integration package entrypoint that exportsmetadata, theapinamespace, and thelibnamespace for consumers inside the monorepo.
These files show two layers of the integration contract. The docs file defines what an operator must configure in HubSpot and in the Cal.diy environment. The package files define how the HubSpot app is exposed to the broader app-store system: the top-level package re-exports metadata and namespaces, while the API namespace exposes named handlers for adding the integration and receiving the OAuth callback. Together, they describe both the deployment checklist and the public package shape that other app-store code can import.
HubSpot Developer App Setup
Start in the HubSpot Developer portal, not in the normal end-user CRM workspace. Sign in or create a developer account, open Manage apps, and create a new app. The docs do not require a specific app name or branding convention; the app information can be filled with whatever is appropriate for the self-hosted deployment. The important part is moving to the Auth tab, because that is where HubSpot displays the OAuth Client ID and Client Secret and where the redirect URL and scopes are configured.
Sources: apps/docs/content/apps/hubspot.mdx
Copy the HubSpot Client ID and Client Secret into the Cal.diy environment. The expected variable names are exact and should be treated as part of the integration contract:
HUBSPOT_CLIENT_ID=your-hubspot-client-id
HUBSPOT_CLIENT_SECRET=your-hubspot-client-secretAfter editing the environment, restart the Cal.diy process or redeploy the service so the application reads the new values. Keep the Client Secret private; it should be handled like any other production secret even when running Cal.diy for personal or non-production use. The docs identify these names as the fields to add to .env, so avoid renaming them or placing them only in a separate secret store unless that store injects the same environment variable names at runtime.
OAuth Callback and Required Scope
The redirect URL configured in HubSpot must point back to the Cal.diy instance that users will actually use. The documented format is:
<Cal.diy URL>/api/integrations/hubspot/callbackReplace <Cal.diy URL> with the public URL of the deployment, including the correct protocol and host. For local testing behind a tunnel, use the tunnel URL that HubSpot can reach. For a production-like deployment, use the same canonical application URL that appears in the browser for Cal.diy users. A mismatch between the public URL, the route registered with HubSpot, and the URL used to open Cal.diy is a common cause of OAuth callback failures in self-hosted integrations.
Sources: apps/docs/content/apps/hubspot.mdx, packages/app-store/hubspot/api/index.ts
The HubSpot app must also request access to contacts. In the Scopes section of the HubSpot developer app, select both Read and Write for crm.objects.contacts. This scope choice matches the documented outcome: when the integration is complete, bookings in Cal.diy can be created as meetings in HubSpot for contacts. The docs specifically call out contact object access rather than broad CRM permissions, so configure the minimal required scope before saving the HubSpot application.
System-to-Code Mapping
The setup guide names the browser callback path, and the HubSpot app-store package exposes a matching API surface. packages/app-store/hubspot/api/index.ts re-exports two default handlers as named exports: add from ./add and callback from ./callback. The callback export is the source-level entrypoint associated with the OAuth return path described in the docs, while add represents the API action for adding or starting the HubSpot integration flow. The evidence here is the barrel export contract, not the internal implementation of each handler.
Sources: packages/app-store/hubspot/api/index.ts, packages/app-store/hubspot/index.ts
The top-level package entrypoint in packages/app-store/hubspot/index.ts makes the HubSpot app available through three public exports. metadata comes from ./_metadata, api is re-exported as a namespace from ./api, and lib is re-exported as a namespace from ./lib. For contributors, this means the package boundary is intentionally organized around descriptive metadata, API handlers, and reusable library functionality. Consumers should prefer this top-level module shape instead of reaching into internal files unless they are modifying the HubSpot integration itself.
| Concern | Source-level contract | Operational meaning |
|---|---|---|
| Credentials | HUBSPOT_CLIENT_ID, HUBSPOT_CLIENT_SECRET | Values copied from the HubSpot developer app into the Cal.diy environment. |
| OAuth return path | /api/integrations/hubspot/callback | Redirect URL configured in HubSpot for the deployed Cal.diy URL. |
| Required scope | crm.objects.contacts Read and Write | Allows Cal.diy booking-related HubSpot behavior for contacts. |
| API exports | add, callback | Named API handlers exposed by the HubSpot app-store API barrel. |
| Package exports | metadata, api, lib | Top-level app-store integration surface for monorepo consumers. |
Verification and Troubleshooting Signals
A healthy configuration has three visible signs. First, the environment contains both HubSpot variables with values copied from the same HubSpot developer app. Second, the HubSpot Auth tab has a redirect URL that exactly matches the public Cal.diy URL plus /api/integrations/hubspot/callback. Third, the app has Read and Write permissions for crm.objects.contacts and has been saved in HubSpot. If the integration cannot be added, re-check the environment names before investigating code, because a typo in either variable prevents the OAuth exchange from using the intended HubSpot app.
Sources: apps/docs/content/apps/hubspot.mdx
When debugging redirects, compare the URL registered in HubSpot with the URL users actually visit. Self-hosted installations often involve reverse proxies, tunnels, or deployment-specific hostnames, and HubSpot only redirects to the exact registered callback. When debugging repository wiring, start from the top-level package export and then the API barrel: packages/app-store/hubspot/index.ts exposes the HubSpot package, and packages/app-store/hubspot/api/index.ts exposes the callback and add handlers. That path confirms that the integration has a defined app-store surface before you inspect deeper implementation files.
Next Steps
After saving the HubSpot developer app and restarting Cal.diy with the required variables, add the HubSpot integration from the Cal.diy app store flow and complete OAuth as an authorized HubSpot user. If you are maintaining the integration, use the package boundary documented here as your starting point: update metadata where the app identity changes, API handlers where authorization flow changes, and library code where shared HubSpot behavior changes. For broader context, read the Apps Overview and the App Store Package and CLI pages next.