HubSpot Service Hub via n8n
This guide connects a WhatsApp line in Spun to HubSpot Service Hub with a ready-made n8n workflow. Every WhatsApp conversation becomes one HubSpot ticket, every message is logged on the ticket as a WhatsApp communication, and agents answer the customer from the ticket.
It builds on Connect a Helpdesk to WhatsApp, which explains the events, signatures and loop guards the workflow relies on. Read that guide first if you plan to change the workflow.
What it does
| Direction | What happens |
|---|---|
| WhatsApp to HubSpot | The first message of a conversation creates a ticket in the pipeline you choose, linked to the customer's contact. The ticket stores the Spun chat id in a custom property. |
| WhatsApp to HubSpot | Every message, the first one too, is logged on the ticket as a WhatsApp communication, prefixed with the customer's name and number. It shows on the contact's timeline as well. Media arrives as a link that stays valid for 24 hours. |
| Spun to HubSpot | Replies your team sends from the Spun inbox are logged on the ticket, so the agent in HubSpot sees the whole exchange. |
| Spun to HubSpot | Archiving the conversation in Spun moves the ticket to your closed stage. Unarchiving it moves the ticket back to your open stage. |
| HubSpot to WhatsApp | An agent types the reply into the ticket's Spun reply field. The workflow sends it to the customer on WhatsApp, logs it on the ticket and clears the field for the next reply. |
HubSpot tickets have no reply box that reaches the customer through the API: notes and communications stay inside HubSpot. That is why replies go through the Spun reply field. Everything else an agent does on the ticket, such as notes, emails or tasks, stays in HubSpot.
Group chats are not supported and are skipped: a group has no phone number, so the workflow cannot link it to a contact or a ticket. One conversation maps to one ticket until that ticket reaches a closed stage; the next message after that starts a new ticket.
Prerequisites
- A HubSpot account with Service Hub and a user who can create custom properties, service keys or private apps, and webhook subscriptions (a super admin, or a user with the Developer tools permission).
- One of these for the webhooks that carry agent replies to n8n:
- a legacy private app that you already have, or create before October 26, 2026 (HubSpot stops the creation of new legacy private apps on that date for existing accounts), or
- a project-based app, which is built with the HubSpot CLI and needs a developer.
- An n8n instance that HubSpot and Spun can reach over HTTPS, either n8n Cloud or a self-hosted install. The workflow uses only the nodes that ship with n8n.
- A Spun workspace with a connected WhatsApp line and access to the Webhooks page in Settings.
Setup
The workflow file and a step-by-step setup document are in the Spun repository under integrations/helpdesk/hubspot-service-hub/. The setup document lists every value with where to find it. In outline:
- Create the two ticket properties in HubSpot under Settings, Properties: Spun chat id (internal name
spun_chat_id, single-line text) and Spun reply (internal namespun_reply, multi-line text). Add Spun reply to the ticket sidebar so agents see it. - Create a key for API calls. A service key or the access token of your legacy private app, with the tickets and contacts read and write scopes and the tickets schema read scope listed in the setup document. In n8n, store it in a Header Auth credential (
Authorization=Bearerfollowed by the key) and select it on every node marked HUBSPOT. - Import the workflow into n8n and run its setup helper once. It lists your ticket pipelines and stages with their ids. Copy the pipeline id, the stage for new tickets, the closed stage Spun should use and every closed stage of the pipeline into the Settings node.
- Tell the workflow whether the Spun CRM contact sync is connected. Keep
crm_sync_connectedset to true if it is, or if you are unsure. See "The CRM contact sync" below. - Choose the reply URL. Replace the placeholder at the end of the Helpdesk reply node's path with a long random string, activate the workflow, and paste the node's production URL into the Settings node exactly as HubSpot will call it.
- Subscribe the app to ticket changes. In the app's webhook settings, set the target URL to the reply URL and add a ticket property change subscription on
spun_reply. Add one on the pipeline stage as well if you want closing in HubSpot to archive the conversation in Spun. Paste the app's client secret into the Settings node. - Create a Spun API key on the Webhooks page under Manage API Keys with the
send_messagescope. Tick "Close & Reopen Conversations" as well (themanage_conversationsscope) if you want a ticket closed in HubSpot to archive the conversation in Spun. Store the key in an n8n Header Auth credential and select it on the three nodes that call Spun. - Subscribe Spun to the workflow. Copy the production URL of the workflow's Spun events node and create a webhook subscription on the Webhooks page for
message.inbound,message.outbound,conversation.closedandconversation.reopened, with the payload template set to raw. Paste the signing secret into the Settings node. - Send a test message to the line from another phone. The Delivery Log on the Webhooks page shows the event, the n8n execution list shows the run, and a new ticket appears in HubSpot. Type a reply into Spun reply on that ticket and save; the reply reaches the phone.
Rotate the signing secrets, the HubSpot key and the Spun API key whenever someone who had access to the n8n instance leaves. HubSpot recommends rotating its keys every six months.
Replying from HubSpot
- Open the ticket and type the reply into Spun reply.
- Save. Within a few seconds the reply is sent to the customer on WhatsApp, logged on the ticket as
[Sent from HubSpot]and the field is empty again. - If the reply could not be sent, a note on the ticket says why, and the field is cleared as well.
Anything written into Spun reply is sent, whoever writes it, including HubSpot workflows and imports. Keep the field out of bulk edits and imports.
The CRM contact sync
Spun also has a CRM contact sync that keeps contacts in step with HubSpot. The two work side by side: the contact sync owns contacts, this workflow owns tickets and the communications on them.
- The workflow uses its own HubSpot key, never the sync's connection. Disconnecting one does not affect the other.
- It looks contacts up by phone number and links them to tickets. It never edits a contact.
- With
crm_sync_connectedset to true, it never creates a contact either. When the sync has not pushed a new customer yet, the ticket is created without a contact and linked on a later message of the same conversation. - With
crm_sync_connectedset to false, it creates a contact with the WhatsApp number, name and, when Spun has one, email address. - Its webhooks go to n8n only. Never point them at the address the contact sync uses.
When Spun has no email address for a contact, the contact sync can clear an email address that an agent typed in HubSpot. Until that changes, add the email address in Spun as well.
Message mapping
| Spun | HubSpot |
|---|---|
First message.inbound of a conversation | New ticket in your pipeline and open stage. Subject "WhatsApp: " followed by the first line of the message text (the caption for media, or the media type such as [image] when there is no caption), cut at 80 characters. Description = the message, Spun chat id set, linked to the contact when one is known. |
Every message.inbound | WhatsApp communication on the ticket and the contact: [WhatsApp] Name (+number): text, with the time of the message. |
| Message after the ticket reached a closed stage | New ticket. |
| Media message | The same communication with the file type and size and a link to the file, valid for 24 hours. The caption follows the link. |
| Media still processing | A communication saying the media is still being processed, with a pointer to the conversation in Spun. The link is not sent later. |
message.outbound sent from the Spun inbox or the phone | Communication: [Sent from Spun] Agent name: text. |
message.outbound that the workflow itself sent | Ignored. |
conversation.closed | Ticket moved to your closed stage. |
conversation.reopened | Ticket moved back to your open stage. |
| Spun reply saved on a ticket | WhatsApp message to the customer, text only, then logged as [Sent from HubSpot] text. |
| Notes, emails and other activity on the ticket | Not sent. |
| Group chat | Skipped. Groups are not supported: they carry no phone number, so no contact or ticket can be created for them. |
Limits
- Closing a ticket in HubSpot archives the conversation in Spun only when you turn that on. The workflow ships with
close_in_spun_when_desk_closesset to false, so closing starts out one-way, from Spun to HubSpot. To enable the other direction, set it to true in the Settings node, give the API key themanage_conversationsscope and add the pipeline stage subscription; the workflow then callsPOST /api/integrations/conversations/closeand/reopen. Check the first close in the n8n execution list before relying on it. Without it, a ticket closed in HubSpot is noticed on the customer's next message, which starts a new ticket. - Replies need the Spun reply field. HubSpot sends no event when an agent writes a note or an email on a ticket, so only the Spun reply field reaches WhatsApp. A reply typed on a ticket the workflow did not create, or on an older ticket of a conversation that has a newer open one, is not sent; the n8n execution says why.
- Replies from HubSpot are text only. Attachments on the ticket are not forwarded to WhatsApp.
- Media is sent as a link, not as a HubSpot file. The link works for 24 hours. A file that was still processing when the message arrived is reported as pending and is not sent afterwards.
- The timeline shows the speaker in the text. HubSpot can only show a HubSpot user as the author of a communication, so every entry starts with
[WhatsApp],[Sent from Spun]or[Sent from HubSpot]instead. - HubSpot limits apply to your HubSpot key. 100 requests per 10 seconds on Free and Starter, 190 on Professional and Enterprise, and a daily limit per account shared with your other integrations. Searches are limited to 5 per second. A new conversation costs four or five requests and a later message two, so ordinary volume stays far below the limits. A request HubSpot refuses fails its n8n execution, which can be re-run; the message stays in the Spun inbox.
- Each delivered event uses AI tokens from your Spun plan. When the budget is exhausted, deliveries are skipped and the Delivery Log shows the reason. Top up or raise the budget to resume. How this is metered for helpdesk use is still being reviewed.
- The workflow remembers tickets and contacts in n8n's workflow data, so it stays well within HubSpot's limits. If you reset the workflow or move it to another n8n instance, the next message for each conversation looks for an open ticket with that chat id in your pipeline and continues from there.
- One active ticket per conversation. Splitting a long conversation into several tickets by day is not supported.
Troubleshooting
- The Delivery Log shows
401from n8n. The Spun signing secret in the Settings node does not match the subscription. Copy it again, or rotate it on the Webhooks page and paste the new value. - The Delivery Log shows a delivery but no ticket appears. Open the n8n execution. A HubSpot error body is kept in the item that failed; the most common causes are a key without the tickets write scope, a pipeline or stage id that does not exist, or a missing
spun_chat_idproperty. - A reply typed into Spun reply never reaches WhatsApp. Check that the property's internal name is exactly
spun_reply, that the app has an active subscription on it pointing at the workflow's reply URL, and that the client secret and the reply URL in the Settings node are current. The n8n execution shows the reason a reply was dropped, for example a signature that did not match. - Every reply is dropped with a signature error. The reply URL in the Settings node must be exactly the URL HubSpot calls, including
httpsand the full path. Copy it from the app's webhook settings. - The Spun reply field does not clear after sending. The key lacks the tickets write scope, or the ticket was deleted. Clear it by hand; the reply was sent only once.
- Tickets are created without a contact. With
crm_sync_connectedset to true this is expected for a brand-new customer until the contact sync has pushed the contact; the next message links it. If it stays unlinked, check that the contact's phone number in HubSpot is the customer's WhatsApp number. - Duplicate contacts. Set
crm_sync_connectedto true when the CRM contact sync is connected, so only the sync creates contacts. - Deliveries show
Skipped: AI token budget exhausted. Top up on the Billing page or raise the budget. Catch up on missed messages withGET /api/integrations/messagesonce deliveries resume.
For the full list of events, headers, retry rules and loop guards, see Connect a Helpdesk to WhatsApp.