Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions integrations/hubspot/definitions/states.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,57 @@ const propertyCacheStates = {
companyPropertyCache: propertyCacheStateDefinition,
} satisfies Record<`${CrmObjectType}PropertyCache`, StateDefinition>

const hitlConfig = {
type: 'integration' as const,
schema: z.object({
channelId: z.string().title('Channel ID').describe('The HubSpot custom channel ID'),
defaultInboxId: z
.string()
.title('Default Inbox ID')
.describe('The inbox used when no inboxId is specified in startHitl'),
channelAccounts: z
.record(z.string())
.title('Channel Accounts')
.describe('Map of inboxId to channelAccountId for all connected inboxes'),
}),
} satisfies StateDefinition

const hitlUserInfo = {
type: 'user' as const,
schema: z.object({
name: z.string().title('Name').describe('The display name of the user'),
contactIdentifier: z.string().title('Contact Identifier').describe('Email address or phone number of the user'),
contactType: z
.enum(['email', 'phone'])
.title('Contact Type')
.describe('Whether the identifier is an email or phone number'),
}),
} satisfies StateDefinition

const hitlSetupWizard = {
type: 'integration' as const,
schema: z.object({
enableHitl: z.boolean().title('Enable HITL').describe('Whether HITL is enabled for this integration'),
selectedInboxIds: z
.array(z.string())
.optional()
.title('Selected Inbox IDs')
.describe('Inboxes selected during wizard setup'),
defaultInboxId: z.string().optional().title('Default Inbox ID').describe('The inbox used by default in startHitl'),
channelId: z
.string()
.optional()
.title('Channel ID')
.describe('HubSpot custom channel ID, saved between wizard steps'),
}),
} satisfies StateDefinition

export const states = {
oauthCredentials,
ticketPipelineCache,
companiesCache,
...propertyCacheStates,
hitlConfig,
hitlUserInfo,
hitlSetupWizard,
} satisfies Record<string, StateDefinition>
86 changes: 78 additions & 8 deletions integrations/hubspot/hub.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
The HubSpot integration allows you to connect your Botpress chatbot with HubSpot, a leading CRM and marketing automation platform. With this integration, your chatbot can manage contacts, tickets, and more directly within HubSpot, enabling seamless automation of sales, marketing, and support workflows.
The HubSpot integration allows you to connect your Botpress chatbot with HubSpot, a leading CRM and marketing automation platform. With this integration, your chatbot can manage contacts, tickets, and more directly within HubSpot, enabling seamless automation of sales, marketing, and support workflows. It also supports Human-in-the-Loop (HITL), allowing conversations to be escalated to HubSpot agents in real time.

## Configuration

To protect the sensitive data in your HubSpot workspace, this integration requires you to create and configure your own private HubSpot app. While we recognize this adds complexity to the setup process, it ensures your data remains secure. We're actively collaborating with HubSpot to streamline this into a one-click setup experience. In the meantime, please follow the steps below to manually configure the integration.
The recommended way to configure this integration is via the built-in **OAuth wizard**, which handles authorization and configuration automatically. You can start the wizard directly from the integration's configuration page in Botpress by clicking on the "Connect/Authorize" button.

### Manual configuration with a custom OAuth app
For advanced users who need full control over their HubSpot app, a **manual configuration** option is also available. Follow the steps below to set it up.

### Manual configuration with a custom private app

1. Install the integration in your bot and copy the webhook URL. This URL starts with `https://webhook.botpress.cloud/`.
2. From your HubSpot settings dashboard, navigate to _Account Management_ &gt; _Integrations_ &gt; _Legacy Apps_.
3. Create a new Legacy App and make it private.
4. Under the _Scopes_ tab, please add the following scopes:
- `oauth`
- `crm.objects.contacts.read`
- `crm.objects.contacts.write`
- `tickets`
Expand All @@ -22,7 +23,8 @@ To protect the sensitive data in your HubSpot workspace, this integration requir
- `crm.objects.deals.read`
- `crm.objects.deals.write`
5. Under the _Webhooks_ tab, paste your webhook URL, set _Event Throttling_ to 1, and click _Create Subscription_.
6. You may now optionally subscribe to webhook events. In the _Create new webhook subscriptions_ dialog, enable _expanded object support_, then select the events you wish to subscribe to. Currently, the integration supports the following events:
6. You may now optionally subscribe to webhook events. In the _Create new webhook subscriptions_ dialog, **you must enable _expanded object support_** before selecting the events you wish to subscribe to. Currently, the integration supports the following events:

- Company Created
- Company Deleted
- Contact Created
Expand All @@ -31,9 +33,77 @@ To protect the sensitive data in your HubSpot workspace, this integration requir
- Lead Deleted
- Ticket Created
- Ticket Deleted
7. You may now click the _Create App_ button to create your Legacy App.
8. From your app's settings page, navigate to the _Auth_ tab and copy the _Access Token_ and _Client Secret_.
9. Paste the _Access Token_ and _Client Secret_ in Botpress, then save the integration's configuration.

7. You may now click the _Create App_ button to create your Legacy Private App.
8. From your app's settings page, navigate to the _Auth_ tab and copy the _Access Token_ and _Client Secret_. In the Botpress integration configuration, paste them and save.

| Field | Value |
| ------------- | ---------------------------------------------------------------- |
| Access Token | The Access Token from your Private App |
| Client Secret | Your app's Client Secret (used for webhook signature validation) |

### HITL (Human-in-the-Loop) manual configuration

If you already have the CRM integration configured, you can reuse the same Private App — just add the HITL scopes and retrieve a few additional values.

#### 1. Add HITL Scopes to Your Private App

In your HubSpot settings, open your existing Private App and add the following scopes (in addition to the CRM scopes already configured):

- `conversations.custom_channels.read`
- `conversations.custom_channels.write`
- `conversations.read`
- `conversations.write`
- `files`

#### 2. Add a Webhook Subscription

Under the **Webhooks** tab, subscribe to:

- `conversation.propertyChange` (for agent assignment and conversation status changes), select all properties.

#### 3. Click _Commit Changes_ to save the updated scopes and webhook subscriptions.

#### 4. Get Your App ID and Developer API Key

- **App ID**: Open your private App in HubSpot again — the App ID is in the URL (e.g., `https://app.hubspot.com/private-apps/ACCOUNT_ID/36900466`).
- **Developer API Key**: In your Hubspot Dashboard, navigate to _Development_ > _Keys_ > _Developer API Key_ and copy or generate your key.

#### 5. Retrieve Your Help Desk or Inbox IDs

You need the ID of the HubSpot inbox (or Help Desk) where HITL conversations will be routed. Use the Access Token from your Private App (found in the _Auth_ tab) to call the inboxes API.

You can run this in a terminal, or use a tool like [Postman](https://www.postman.com/) or [ReqBin](https://reqbin.com/). Replace `YOUR_ACCESS_TOKEN` with the token from your Private App:

```bash
curl --location 'https://api.hubapi.com/conversations/v3/conversations/inboxes' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN'
```

The response will list all inboxes in your HubSpot account. Look for the entry matching your target inbox and copy its `id`. For Help Desk, look for `"type": "HELP_DESK"`. For a standard inbox, look for `"type": "INBOX"`.

```json
{
"results": [
{ "id": "1431487401", "name": "Help Desk", "type": "HELP_DESK" },
{ "id": "1234567890", "name": "Sales Inbox", "type": "INBOX" }
]
}
```

You can connect multiple inboxes — the first one will be used as the default.

#### 6. Configure Botpress

Fill in the following fields in your Botpress integration configuration:

| Field | Value |
| ----------------- | ------------------------------------------------------------------------- |
| App ID | Your app's App ID from step 4 |
| Developer API Key | Your developer API key from step 4 |
| Inbox IDs | One or more inbox or Help Desk IDs from step 5. The first is the default. |

Save the configuration. After saving, it may take **over a minute** for the HubSpot custom channel to connect. Do not refresh or close the page during this time.

## Migrating from 4.x to 5.x

Expand Down
74 changes: 70 additions & 4 deletions integrations/hubspot/integration.definition.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { IntegrationDefinition, z } from '@botpress/sdk'
import hitl from './bp_modules/hitl'
import { actions, states, events } from './definitions'

export default new IntegrationDefinition({
name: 'hubspot',
title: 'HubSpot',
description: 'Manage contacts, tickets and more from your chatbot.',
version: '5.3.3',
version: '6.0.0',
readme: 'hub.md',
icon: 'icon.svg',
configuration: {
Expand All @@ -19,13 +20,32 @@ export default new IntegrationDefinition({
title: 'Manual Configuration',
description: 'Manual configuration, use your own Hubspot app',
schema: z.object({
accessToken: z.string().min(1).secret().title('Access Token').describe('Your Hubspot Access Token'),
accessToken: z
.string()
.min(1)
.secret()
.title('Access Token')
.describe('Your HubSpot Private App Access Token.'),
clientSecret: z
.string()
.secret()
.optional()
.title('Client Secret')
.describe('Hubspot Client Secret (used for webhook signature check)'),
.describe('Your HubSpot app Client Secret. Used for webhook signature validation.'),
inboxIds: z
.array(z.string())
.optional()
.title('Inbox or Help Desk IDs')
.describe(
'List of HubSpot Inbox or Help Desk IDs. The first ID is the default. Works with both HubSpot Inbox (Sales Hub) and Help Desk (Service Hub).'
),
developerApiKey: z
.string()
.secret()
.optional()
.title('Developer API Key')
.describe('Required for HITL. Found in your HubSpot developer portal.'),
appId: z.string().optional().title('App ID').describe('Required for HITL. The ID of your HubSpot app.'),
}),
},
},
Expand All @@ -35,6 +55,21 @@ export default new IntegrationDefinition({
actions,
events,
states,
entities: {
ticket: {
schema: z.object({
inboxId: z.string().optional().title('Inbox ID').describe('Override the default inbox for this HITL session'),
}),
},
},
user: {
tags: {
email: { title: 'Email', description: 'Email address of the user' },
phoneNumber: { title: 'Phone Number', description: 'Phone number of the user' },
contactType: { title: 'Contact Type', description: 'Whether the user was identified by email or phone' },
actorId: { title: 'Actor ID', description: 'HubSpot actor ID' },
},
},
secrets: {
CLIENT_ID: {
description: 'The client ID of the Hubspot app',
Expand All @@ -46,6 +81,13 @@ export default new IntegrationDefinition({
// TODO: Remove once the OAuth app allows for unlimited installs
description: 'Whether to disable OAuth',
},
APP_ID: {
description: 'HubSpot app ID for the Botpress OAuth app, used for Custom Channels (HITL)',
},
DEVELOPER_API_KEY: {
description:
'HubSpot developer API key (hapikey) for the Botpress OAuth app, required to create/manage Custom Channels',
},
},
__advanced: {
useLegacyZuiTransformer: true,
Expand All @@ -55,4 +97,28 @@ export default new IntegrationDefinition({
guideSlug: 'hubspot',
repo: 'botpress',
},
})
}).extend(hitl, (self) => ({
entities: {
hitlSession: self.entities.ticket,
},
channels: {
hitl: {
title: 'HubSpot HITL',
description: 'Human-in-the-Loop channel for routing conversations to HubSpot agents',
conversation: {
tags: {
id: { title: 'HubSpot Conversation ID', description: 'The HubSpot conversations thread ID' },
userId: { title: 'Botpress User ID', description: 'The ID of the Botpress user for this HITL session' },
integrationThreadId: {
title: 'Integration Thread ID',
description: 'The UUID used as integrationThreadId in HubSpot Custom Channel messages',
},
inboxId: {
title: 'Inbox ID',
description: 'The HubSpot inbox ID used for this HITL session',
},
},
},
},
},
}))
29 changes: 2 additions & 27 deletions integrations/hubspot/linkTemplate.vrl
Original file line number Diff line number Diff line change
@@ -1,29 +1,4 @@
env = to_string!(.env)
clientId = if env == "production" {
"5eec0a6a-5be0-482f-8c5d-8ee139bcba43"
} else {
"eaebade6-ace5-452b-83ed-05418def8e5b"
}

webhookUrl = to_string!(.webhookUrl)
redirectUri = "{{ webhookUrl }}/oauth"
webhookId = to_string!(.webhookId)
webhookUrl = to_string!(.webhookUrl)

scopes = [
"oauth",
"crm.objects.contacts.read", # to retrieve contacts
"crm.objects.contacts.write", # to create and update contacts
"tickets", # to retrieve ticket properties and create tickets
#"tickets.sensitive", # to retrieve sensitive ticket properties
#"tickets.highly_sensitive", # to retrieve highly sensitive ticket properties
"crm.objects.owners.read", # to retrieve and assign owners to tickets
"crm.objects.companies.read", # to retrieve and assign companies to tickets
"crm.objects.companies.write", # to create and update companies
"crm.objects.leads.read", # to retrieve leads
"crm.objects.leads.write", # to create and update leads
"crm.objects.deals.read", # to retrieve deals
"crm.objects.deals.write", # to create and update deals
]
scopesStr = encode_percent(join!(scopes, " "))

"https://app.hubspot.com/oauth/authorize?client_id={{ clientId }}&redirect_uri={{ redirectUri }}&state={{ webhookId }}&scope={{ scopesStr }}"
"{{ webhookUrl }}/oauth/wizard/start?state={{ webhookId }}"
10 changes: 8 additions & 2 deletions integrations/hubspot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"name": "@botpresshub/hubspot",
"description": "Hubspot integration for Botpress",
"private": true,
"bpDependencies": {
"hitl": "../../interfaces/hitl"
},
"scripts": {
"build": "bp add -y && bp build",
"check:type": "tsc --noEmit",
Expand All @@ -11,10 +14,13 @@
"dependencies": {
"@botpress/common": "workspace:*",
"@botpress/sdk": "workspace:*",
"@hubspot/api-client": "^13.1.0"
"@hubspot/api-client": "^13.1.0",
"preact": "^10.26.6",
"preact-render-to-string": "^6.5.13"
},
"devDependencies": {
"@botpress/cli": "workspace:*",
"@botpress/sdk": "workspace:*"
"@botpress/sdk": "workspace:*",
"@types/node": "^22.16.4"
}
}
Loading
Loading