Alright, so we've been running Lindy for internal process automation for a few months now, and one of the first big hurdles was getting it to properly talk to our HubSpot CRM. The out-of-the-box "Connect to HubSpot" is fine for a basic read-only glance, but if you want Lindy to actually *do* things – update deal stages, create notes, find contacts based on complex criteria – you need to set up a proper private app integration. Here's how we did it, including the gotchas that cost us an afternoon.
First, you can't use a regular OAuth app from HubSpot. Lindy needs a persistent, unattended connection, so you have to create a **HubSpot Private App**. This gives you an access token that doesn't expire every few hours.
**Steps on the HubSpot side:**
1. In your HubSpot portal, go to Settings > Integrations > Private Apps.
2. Create a new private app. You need to be a super admin.
3. Scopes are critical. We granted the following for our use cases:
- `crm.objects.contacts.read`
- `crm.objects.contacts.write`
- `crm.objects.deals.read`
- `crm.objects.deals.write`
- `crm.objects.companies.read`
- `timeline` (for creating notes/emails on the timeline)
4. Copy the generated access token. This is your only chance to see it in plain text.
Now, in Lindy, you don't use the standard HubSpot "provider." You configure it as a **Custom API Integration**. Here's the core configuration we used in the Lindy "Configure Integration" JSON field:
```json
{
"base_url": "https://api.hubapi.com",
"auth": {
"type": "bearer",
"token": "YOUR_PRIVATE_APP_TOKEN_HERE"
},
"headers": {
"Content-Type": "application/json"
}
}
```
The real work is in defining the specific actions. Lindy's "Custom API" setup requires you to manually define each endpoint as an "action." For example, here's our action for searching for a contact by email:
```yaml
name: hubspot_find_contact_by_email
description: Search for a HubSpot contact by email address
endpoint: /crm/v3/objects/contacts/search
method: POST
request_body: |
{
"filterGroups": [
{
"filters": [
{
"propertyName": "email",
"operator": "EQ",
"value": "{{email}}"
}
]
}
],
"properties": ["firstname", "lastname", "email", "hs_object_id"]
}
response_mapping: |
{
"contact_id": "body.results[0].id",
"properties": "body.results[0].properties"
}
```
**Key pitfalls we encountered:**
* HubSpot's API uses `hs_object_id` for the actual record ID, but you need to request it as a property. The `id` in the root of the result object is *also* needed for updates. It's confusing.
* For creating timeline notes, the endpoint is `/crm/v3/timeline/events`. The event template ID is a portal-specific UUID you have to find in the HubSpot UI first.
* Rate limiting is strict. We had to add delays in our Lindy automations when batch-processing deals.
* Always filter for `archived: false` in your searches unless you explicitly need old data.
This setup is now powering automations where Lindy:
* Flags deals in HubSpot that have gone stagnant and pings the channel in Slack.
* Creates a contact and a linked deal record when a high-intent form is submitted on our site (form handled elsewhere, webhook to Lindy).
* Generates and attaches a simple summary note to a company record after a sales call (transcript analyzed by another Lindy).
It's not plug-and-play, but once the custom actions are built, they're solid. The main benefit over Zapier/Make is that all the logic – the decision trees, the data formatting – lives alongside our other Lindy automations, not in another siloed platform.
Automate everything. Twice.