intermediate

HubSpot lead capture chatbot for n8n, with consent tracking

An n8n chatbot that answers questions, collects details by conversation and writes a tagged contact to HubSpot, adding people to a list only after explicit consent.

ChatbotAI agentCRMLead capture

Prerequisites

  • An n8n instance, cloud or self-hosted
  • A HubSpot account that allows private apps and custom contact properties
  • A HubSpot private app token with crm.objects.contacts.read, crm.objects.contacts.write, crm.schemas.contacts.write and crm.lists.write scopes
  • A static list in HubSpot for opted-in contacts
  • An OpenAI API key (or another chat model credential)
  • Your FAQ or services copy as plain text

A chat widget that answers visitor questions, collects their details through the conversation, and writes a contact straight into HubSpot. Marketing consent is its own separate, recorded step.

What you'll build

A visitor asks a question and the bot answers it from your own content. If there is a reason, it asks for an email so someone can follow up. n8n then creates or updates the HubSpot contact, sets properties describing what they were interested in and how ready to buy they sounded, and writes a short summary onto the record.

Three things separate this from a form that emails you:

  • The properties are specific. chat_interest: pricing and chat_intent: hot come from what the visitor said, so your existing HubSpot views and workflows can act on them.
  • The consent fields are populated. There is a timestamp and the exact wording the bot used when it asked.
  • The summary is readable. Whoever opens the contact gets the context in two sentences instead of a transcript.

How it works

  • A webhook receives each widget message and checks a shared secret.
  • An AI agent answers from your knowledge text and asks for an email only when there is a reason.
  • The capture_lead tool is a separate workflow. It saves the contact in HubSpot, which has no single-call upsert, so it tries an update by email first and creates the contact only if that comes back not found.
  • Your agent's choices land in custom contact properties: chat_interest, chat_intent and chat_objection are dropdowns, and the tool only accepts the allowed values.
  • List membership is a separate step that only happens after consent, using a static list you create yourself.

What it costs

The workflow is free to download and use. Your AI provider charges per conversation, and your n8n hosting and the plan for the service you connect apply as normal. For N8N Chat UI plans, see pricing.

Set it up

  1. Create a private app. In HubSpot go to Settings → Integrations → Private Apps, grant the four scopes listed above and copy the access token. HubSpot only shows it once.
  2. Create a list. In Contacts → Lists, create a static list for opted-in contacts and note its ID from the URL.
  3. Create the credential. In n8n, create an HTTP Header Auth credential with Name Authorization and Value Bearer YOUR_PRIVATE_APP_TOKEN.
  4. Import the workflow. Download the file with the button at the top of this page and import it with Workflows → Import from File. It holds the main chatbot and its tool workflow on one canvas, each in a labelled section.
  5. Move each tool into its own workflow. n8n tools call a separate workflow, so for each tool section below the main flow: select its nodes, copy them, create a new empty workflow and paste. Save it, then open the matching tool node under the agent and select that workflow. Do the same for the setup section, then attach your HubSpot credential to every HTTP Request node in both workflows.
  6. Run the setup. In the setup workflow click Execute. It creates the properties chat_session_id, chat_first_seen_url, chat_last_summary, chat_consent_timestamp, chat_consent_wording, chat_interest, chat_intent and chat_objection. It is safe to run again: properties that exist are skipped. A 403 here means the private app is missing the crm.schemas.contacts.write scope.
  7. Fill in the settings. In Chatbot settings set company_name, consent_wording, knowledge and a random webhook_secret. In the capture lead workflow's Tool settings set list_id to the ID from step 2.
  8. Add your AI credential. Open GPT-4.1 mini model and add your OpenAI credential. Swap the node for another chat model if you prefer.

Test it

Send a test message before you connect a widget. n8n's built-in chat panel needs a Chat Trigger, so it will not open for this workflow. Instead, click Test workflow, then send a POST request to the Test URL shown on the Chat webhook node:

curl -X POST 'YOUR_TEST_URL' \
  -H 'Content-Type: application/json' \
  -d '{"chatInput":"Hello","sessionId":"test-1","metadata":{"webhook_secret":"YOUR_SECRET"}}'

The test URL listens for one request each time you click Test workflow. For a longer conversation, activate the workflow and send requests to the production URL, then read each run under Executions. Reuse the same sessionId to continue a conversation, because memory is kept per session.

Things to try:

  • Ask a question and give no email. No contact should be created.
  • Give an email and decline marketing. The contact exists with properties filled in and is not on your list.
  • Give an email and agree. The contact is on your list with the consent timestamp and wording populated.
  • Come back with the same email. The contact is updated, not duplicated.
  • Ask something outside your knowledge text. The bot should say it is not sure.

Add it to your site

Activate the workflow and use its production webhook URL as your widget's chat endpoint. Send the same secret in the widget metadata, for example metadata: { page_url: window.location.href, webhook_secret: 'YOUR_SECRET' }. The metadata docs explain how metadata reaches n8n.

Any chat front end that can POST chatInput, sessionId and metadata to a webhook and read { "output": "..." } back will work. N8N Chat UI is built for exactly this.

Getting someone's email so you can answer their question, and adding them to a marketing list, are two different things. Treating them as one is how businesses end up with complaints they cannot answer. Many chatbot-to-CRM setups subscribe anyone who types an email address, which is a problem under UK GDPR and the EU equivalent, where consent has to be specific and freely given rather than bundled into something else the person wanted.

This workflow splits it in two. The agent never writes to the CRM directly. When it has an email and a reason to save it, it calls the capture_lead tool with consent_given set to false, and the contact is created but subscribed to nothing. Only after a separate, explicit yes does it call the tool a second time with consent_given set to true. That second call adds the contact to your list and records the exact wording the bot used and a timestamp.

The agent is told that ambiguity is not consent. "Sure, whatever" in reply to a bundled question does not count, and it is instructed never to bundle the two asks. Because the tool is a separate workflow, the gate is ordinary n8n branching that either fires or does not, not a suggestion to the model.

None of this is legal advice, and your obligations depend on where you and your contacts are. What it gives you is a record: if someone asks how they ended up on your list, the answer is on their contact record with a date against it.

Troubleshooting

I see a 404 before a successful call for a brand new contact

Expected. The tool tries an update first, and for a contact that does not exist yet HubSpot returns a 404, which shows in the output of Try update contact. The tool then creates the contact. Returning visitors update in one call.

I get a 403 from HubSpot

Check the private app has all four scopes. A missing scope fails on whichever call needs it, not necessarily the first one.

The contact exists but the properties are empty

The setup workflow did not finish, or ran against a different HubSpot account than your token points at. Run it again.

The bot replies but the tool never runs

The tool node is not linked to its workflow. Open it and select the workflow you created from the tool section. A bare placeholder ID means it is still unlinked.

My conversations reset when n8n restarts

Memory is an in-memory buffer so the template works as soon as you import it. A restart or redeploy drops conversations in progress. Before a busy launch, swap Conversation memory for Postgres Chat Memory. Nothing else changes.

I get a 401 as soon as I test

The Request from your widget? check compares metadata.webhook_secret in the request with webhook_secret in Chatbot settings. If they differ, or the request has no metadata, the workflow returns a 401 before the agent runs.

Take it further

This recipe creates contacts and list membership, not deals. Deals are a heavier object with their own pipeline, and a lead capture recipe should not assume how yours is structured. To use a different CRM, swap the HTTP Request nodes in the capture lead workflow and keep the agent, the consent gate and the property logic as they are. See also the ActiveCampaign and Mailchimp versions.