HubSpot Integration
Connect your agent to HubSpot for contact, deal, and engagement management via the HubSpot CRM API.
Overview
The HubSpot integration uses HubSpot's OAuth 2.0.
Skills that use this integration:
int-hubspot-crm— Contact/deal CRUD, note and task creation, sequence enrollmentutil-newsletter-compose— optional; uses HubSpot as an ESP when enabled alongside
Agents that typically enable this integration:
- Lead Generation Manager
- Newsletter Manager (HubSpot as ESP)
- Any custom agent with CRM capabilities
Availability
| Mode | Status | Notes |
|---|---|---|
"wiro" |
Available | One-click connect using Wiro's HubSpot app. |
"own" |
Available | Your own HubSpot developer app. |
Prerequisites
- A Wiro API key — Authentication.
- A deployed agent — Agent Overview.
- A HubSpot account the connecting user is an admin of.
- (Own mode) A HubSpot developer account — developers.hubspot.com.
- An HTTPS callback URL.
Wiro Mode
Call OAuthConnect with credentialkey: "hubspot" and without authmethod, redirect, parse hubspot_connected=true&hubspot_portal=<id>&hubspot_name=<name>.
Complete Integration Walkthrough — Own Mode
Step 1: Create a HubSpot App
- developers.hubspot.com → sign in → Create app.
- Set App name and App description (shown on consent).
Step 2: Configure Auth
- Open the Auth tab.
-
Redirect URL:
https://api.wiro.ai/v1/UserAgentOAuth/HubSpotCallback -
Scopes — Wiro requests a fixed scope string plus optional_scopes. Enable all of the following in your HubSpot app's Auth tab:
Required
scope(mandatory — OAuth fails if any is missing):crm.objects.contacts.readcrm.objects.contacts.writecrm.lists.readcrm.lists.writeoauth
optional_scope(granted on consent if enabled, otherwise skipped — Wiro doesn't fail if missing):crm.objects.companies.readcrm.objects.companies.writecrm.objects.deals.readcrm.objects.deals.writecrm.objects.owners.readcrm.schemas.contacts.readcontenttransactional-emailfiles
- Save.
Wiro's authorize URL is built with this exact scope list — you cannot customize it per integration. Enabling additional scopes in your HubSpot app beyond this set has no effect (Wiro won't request them). If a required scope is missing from your app's configuration, the consent screen will error.
Step 3: Copy Client ID and Client Secret
Auth tab → copy Client ID and Client Secret.
Step 4: Save credentials to Wiro
curl -X POST "https://api.wiro.ai/v1/UserAgent/CredentialUpsert" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"useragentguid": "your-useragent-guid",
"fields": [
{ "credentialkey": "hubspot", "fieldname": "clientid", "fieldvalue": "YOUR_HUBSPOT_CLIENT_ID" },
{ "credentialkey": "hubspot", "fieldname": "clientsecret", "fieldvalue": "YOUR_HUBSPOT_CLIENT_SECRET" },
{ "credentialkey": "hubspot", "fieldname": "authmethod", "fieldvalue": "own" }
]
}'
Step 5: Initiate OAuth
curl -X POST "https://api.wiro.ai/v1/UserAgentOAuth/OAuthConnect" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"useragentguid": "your-useragent-guid",
"credentialkey": "hubspot",
"redirecturl": "https://your-app.com/settings/integrations",
"authmethod": "own"
}'
Response:
{
"result": true,
"authorizeUrl": "https://app.hubspot.com/oauth/authorize?client_id=...&redirect_uri=...&scope=...&state=...",
"errors": []
}
Step 6: Handle the callback
Wiro exchanges the code, then calls GET https://api.hubapi.com/oauth/v1/access-tokens/<access_token> to fetch hub_id (portalid) and hub_domain (portalname).
Success URL:
https://your-app.com/settings/integrations?hubspot_connected=true&hubspot_portal=12345678&hubspot_name=My%20Workspace
Step 7: Verify and Start
curl -X POST "https://api.wiro.ai/v1/UserAgentOAuth/OAuthStatus" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"useragentguid": "your-useragent-guid",
"credentialkey": "hubspot"
}'
Response:
{
"result": true,
"connected": true,
"accounts": [
{ "id": "Acme Corp HubSpot", "name": "Acme Corp HubSpot" }
],
"connectedat": "2026-04-17T12:00:00.000Z",
"tokenexpiresat": "2026-04-17T12:30:00.000Z",
"errors": []
}
HubSpot tokens expire in 30 minutes. Wiro maintains the connection automatically while the agent is running. If authorization is revoked, reconnect HubSpot through the OAuth flow.
Note: For HubSpot, the unified OAuthStatus mirrors the saved portalname into both accounts[0].id and accounts[0].name (HubSpot has no separate picker — there's exactly one portal per OAuth grant). The numeric portalid is captured by HubSpotCallback as hubspot_portal=<id> on the redirect URL but is not re-surfaced by OAuthStatus.
curl -X POST "https://api.wiro.ai/v1/UserAgent/Start" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{ "guid": "your-useragent-guid" }'
API Reference
POST /UserAgentOAuth/OAuthConnect
| Parameter | Type | Required | Description |
|---|---|---|---|
useragentguid |
string | Yes | Agent instance GUID. |
credentialkey |
string | Yes | "hubspot". |
redirecturl |
string | Yes | HTTPS URL. |
authmethod |
string | No | "wiro" (default) or "own". |
GET /UserAgentOAuth/HubSpotCallback
Query params: hubspot_connected=true&hubspot_portal=<id>&hubspot_name=<name> or hubspot_error=<code>. The callback path is per-provider — HubSpot's stays HubSpotCallback.
POST /UserAgentOAuth/OAuthStatus
Body: { useragentguid, credentialkey: "hubspot" }. Response: connected, accounts: [{id, name}] (1-element with the saved portalname mirrored into both id and name), connectedat, tokenexpiresat (~30 min).
POST /UserAgentOAuth/OAuthDisconnect
Body: { useragentguid, credentialkey: "hubspot" }. Clears HubSpot credentials (no remote revoke).
Token lifecycle
Wiro maintains the HubSpot connection automatically while the agent is running. If HubSpot revokes the authorization or OAuthStatus reports connected: false, reconnect through OAuth.
Using the Skill
curl -X POST "https://api.wiro.ai/v1/UserAgent/CustomSkillUpsert" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"useragentguid": "your-useragent-guid",
"skillkey": "lead-enrichment",
"enabled": true,
"interval": "0 */4 * * *",
"value": "Enrich new contacts with company information"
}'
Troubleshooting
| Error code | Meaning | What to do |
|---|---|---|
missing_params |
Callback hit without state or code. |
User didn't complete consent; restart the flow. |
authorization_denied |
User cancelled, or missing scopes. | Verify scope list in the HubSpot app's Auth tab. |
session_expired |
State cache expired (15 min TTL). | Restart the OAuth flow. |
token_exchange_failed |
Wrong Client Secret or redirect URI mismatch. | Re-copy; verify URL. |
useragent_not_found |
Invalid guid. | Use POST /UserAgent/MyAgents. |
HubSpot credentials not configured |
Returned in OAuthConnect's errors[] when authmethod: "own" but clientid / clientsecret are missing. |
UserAgent/CredentialUpsert with clientid + clientsecret. |
internal_error |
Server error. | Retry; contact support. |
403 Forbidden on API calls
Usually a missing scope. Look up the specific HubSpot API endpoint you're hitting, add the required scope in your app's Auth tab, then disconnect and reconnect (scope changes require re-consent).
Token expired error at runtime
HubSpot's 30-minute token lifetime makes continuous authorization important. If you see a token-expired error:
- The agent is stopped (status 0/1/6). Start it:
POST /UserAgent/Start. - The authorization may have been revoked in HubSpot's app management UI. Reconnect the user.
- If the agent is running and reconnection does not resolve it, contact Wiro support.
Multi-Tenant Architecture
- One HubSpot developer app per product — submit to the HubSpot App Marketplace for listed visibility, or stay private.
- One Wiro agent instance per customer.
- Portal IDs are unique per customer's HubSpot account.
- Per-app rate limits apply — see HubSpot's API usage guidelines.