Routing website leads to the right owner in HubSpot with n8n
When website leads land in a shared inbox, someone has to copy them into HubSpot and decide who follows up. An n8n workflow can do both the same way every time, and tell you when it could not.
The workflow
- Webhook: the form posts its data over HTTPS to a Webhook node in n8n. Let a Respond to Webhook node send the response, so the form gets an answer that reflects what actually happened.
- Validation and honeypot: check required fields and the email format, trim whitespace and lowercase the email. The form has a hidden field that people never see; if it contains a value, return a normal success response, log the hit and stop, so the bot learns nothing.
- Upsert the contact by email: use the HubSpot node’s “Create/Update a contact” operation, with the email address as the match. A returning lead updates the existing contact instead of creating a second one.
- Owner routing: a Switch node picks the owner by service or budget band, with a default owner for anything that does not match. Set the contact owner property (hubspot_owner_id) using owner IDs from HubSpot’s owners API, not names. Decide whether an existing contact keeps its owner; if so, look the contact up first and only set the owner when it is empty.
- Slack mention: post the lead summary and a link to the HubSpot record to the sales channel. To actually notify someone, the message must contain their Slack member ID in the <@MEMBER_ID> format; @name typed as plain text pings nobody. Keep a small table mapping each HubSpot owner to a Slack member ID.
- Acknowledgement email: send the lead a short email saying what happens next and when. Send it once the lead is safely stored, not before.
- Failure-alert path: on the HubSpot node, turn on Retry On Fail with a wait between tries to ride out brief outages. If it still fails, use the node’s error output to write the lead to a fallback store (a sheet or data table), alert the operations channel and still send the acknowledgement. A shared error workflow catches anything unexpected.
Field mapping you can copy
Write the mapping down before you build: one row per form field, with the HubSpot property’s internal name, its type, the transformation, what happens when the field is empty and whether an update may overwrite it. The example assumes a contact form with a service dropdown, a budget dropdown and a marketing consent checkbox; replace the rows with your own fields.
| Form field | HubSpot property (internal) | Type | Transformation | If empty | On update |
|--------------|-----------------------------|-------------|----------------------------|----------------|------------------|
| email | email | string | trim, lowercase | reject (400) | match key |
| first_name | firstname | string | trim | leave blank | overwrite |
| last_name | lastname | string | trim | leave blank | overwrite |
| company | company | string | trim | leave blank | keep existing |
| phone | phone | string | trim; keep as typed | leave blank | overwrite |
| service | service_interest (custom) | enumeration | label -> option value | other | overwrite |
| budget | budget_band (custom) | enumeration | label -> option value | unknown | overwrite |
| message | enquiry_message (custom) | string | trim; cut at agreed length | leave blank | overwrite |
| page_url | enquiry_page (custom) | string | strip query string | leave blank | overwrite |
| utm_source | utm_source (custom) | string | lowercase | leave blank | keep existing |
| (submit time)| enquiry_date (custom) | date | ISO date, e.g. 2026-11-09 | n/a | overwrite |
| consent_box | marketing_consent (custom) | bool | checked -> true | false | only false->true |
| (routing) | hubspot_owner_id | owner ID | from the routing table | default owner | keep if set |
| (fixed) | lifecyclestage | enumeration | internal value for Lead | n/a | only if empty |Two choices in the example are deliberate. Company and UTM source keep their existing values on update, so a returning contact keeps the campaign that first brought them in. Consent only moves from false to true here; withdrawing consent goes through your unsubscribe or preference process, not through a form that arrives without the box ticked.
Internal names and dropdown values
HubSpot’s Properties API identifies a property by its internal name; the label is only what people see, and the internal name cannot be edited after the property is created. Custom properties such as service_interest must exist before the workflow writes to them. A dropdown is an enumeration: send each option’s stored value rather than its label, keep a small lookup from form values to option values, and send a default such as other for anything unexpected. For multiple checkboxes, send the selected values as one string separated by semicolons, without spaces.
Dates
HubSpot stores date values in UTC. For a date-only property, a timestamp must be in epoch milliseconds at midnight UTC, and ISO 8601 (for example 2026-11-09) is the recommended format. A form sent at 00:30 in Istanbul is still the previous day in UTC, so decide whose day counts and convert on purpose.
Fields HubSpot manages itself
Lifecycle stage can only be moved forward by HubSpot tools, including the API; to set an earlier stage, the value has to be cleared first. Set the Lead stage only when the field is empty, so existing customers are not touched. Leave HubSpot’s own source tracking alone and keep campaign parameters in your own properties, as in the utm_source row.
What an update may overwrite
Because the workflow updates a returning contact instead of creating a new one, every row needs an “On update” rule: overwrite, keep existing, or a one-way rule like the consent row. Watch the owner: if routing runs on every submission, a returning lead can be reassigned away from the person already talking to them.
The Slack alert
The alert is a mapping too: show labels to people and keep values in HubSpot. Slack only notifies someone when the message contains their user ID as <@U012AB3CD>, and its <url|text> format turns the record link into “Open in HubSpot”.
New lead: {firstname} {lastname}, {company}
Service: {service_interest label} Budget: {budget_band label}
Owner: <@{slack_id_for_owner}>
<{hubspot_record_url}|Open in HubSpot>To test the mapping, send one submission per interesting row: every dropdown option plus one unknown value, an empty optional field, a returning email in different letter case, a submission just after midnight, and the consent box both ticked and unticked. Open the contact after each and check every property against the table, then run the five acceptance tests below.
The five acceptance tests
- A new lead: a valid submission creates one contact with the right owner, one Slack message mentioning that owner and one acknowledgement email.
- A duplicate: submitting the same email again (in different letter case) updates the existing contact; the contact count does not change and your ownership rule is followed.
- An invalid email: the form gets a clear validation error; nothing is written to HubSpot and no Slack message or email is sent.
- A honeypot hit: the form sees a normal success response; nothing reaches HubSpot, Slack or the sender, and the hit is logged.
- The CRM down: with HubSpot unreachable (for example, an invalid access token in a test copy of the workflow), the lead is written to the fallback store, the operations alert fires once, the lead still gets the acknowledgement, and replaying the stored lead later creates exactly one contact.
Pitfalls
- Field mapping and dates: follow the table and rules in “Field mapping you can copy” above.
- Rate limits: HubSpot limits API calls per time window, and the limits depend on the account’s subscription and the type of app. Requests over the limit get HTTP 429. Retry with a wait, avoid extra lookups per lead, and when backfilling old leads, send them in batches instead of all at once.
- Credentials: use a private app access token with only the scopes the workflow needs (read and write contacts, and read owners if you look them up).
- Testing: test in a test account where possible, or with clearly marked test emails, and delete the test records afterwards.
- Retries of the same submission: give each form submission a unique ID and check it before writing, so a retried request does not create a second record while two genuine enquiries from the same person stay separate.
- Personal data: decide how long enquiries are kept in the CRM, the fallback store and n8n's execution history, and delete them on that schedule.
Live demo
You can try this kind of routing workflow in the live demo. It runs on n8n and shows the CRM record, owner, Slack alert and auto-reply it would produce, without sending anything. Agencies that want this built for a client under their own brand can read about it on the agency partners page.
FAQ
Where do I find a HubSpot property’s internal name?
In the property’s details in HubSpot’s property settings, or from the Properties API, where it is the name field. It cannot be edited after the property is created, so the workflow can rely on it.
Why does my dropdown value not save in HubSpot?
Check whether the workflow sent the label people see instead of the option’s stored value, and whether the value is defined as an option at all. Map form values to option values and send a default for anything unknown.
Can the workflow set the lifecycle stage?
Yes, but the API can only move it forward. Set it only when the field is empty, so existing customers are not touched.
Does this work with Pipedrive, Airtable or Google Sheets instead of HubSpot?
Yes. The webhook, validation, alert, acknowledgement and failure path stay the same. What changes is the step that writes the record: the field that identifies a returning person, the property names in the mapping table and how an owner is stored.
Sources
- HubSpot Developers, “Properties API”: developers.hubspot.com (internal name and label; property types; date values in UTC, midnight UTC for date properties, ISO 8601; semicolon-separated checkbox values).
- HubSpot Knowledge Base, “Create and edit properties”: knowledge.hubspot.com (the internal name cannot be edited).
- HubSpot Knowledge Base, “Use lifecycle stages”: knowledge.hubspot.com (forward-only updates by HubSpot tools, including the API).
- n8n Docs, “HubSpot node”: docs.n8n.io (contact operations, including Create/Update a contact).
- Slack Developer Docs, “Formatting message text”: docs.slack.dev (user mentions and links with custom text).
Related project and service
Need help with this? Tell us about it.
Broken n8n workflow? Send us the workflow and the error, and we find the cause, fix it and test it, with a one-day delivery option.
Tell us about the workflow