Small BusinessIndependent InvestorTrade BusinessAccountingFinanceLegalMarketingOperationsSalesReal EstateTemplatesPricingDocs
Get started

Step Guide: HubSpot

Business Systems

Automate HubSpot contact and company reads, writes, and notes from Formt workflows

Overview

The HubSpot step connects your workflow to a HubSpot portal so it can read contacts and companies, write them back, and log activity. Six actions cover the full CRM loop. Requires a connected HubSpot integration.

Read Contact List

Fetch members of a saved HubSpot list (static or dynamic) as the data source for a workflow. Pick the list from a dropdown. The editor pulls your portal's saved lists live.

  • Pull a marketing list of inbound leads → ForEach → personalised outreach
  • Read an "MQLs from last week" dynamic list → enrich each one → upsert results back
  • Use a saved list as a recurring data source for a scheduled workflow

Pulls up to 5,000 members per run. Each contact comes back with the standard fields (email, first name, last name, phone, company, job title) plus HubSpot metadata (record ID and timestamps). You'll reference these in downstream steps using the HubSpot property names: firstname, lastname, phone, company, jobtitle, etc. Output is a list. Connect to a ForEach to iterate. If your list has more than 5,000 members, the output's list_total field tells you the real count so you can see when truncation happened.

Tips
  • Pair with a ForEach to walk through every contact in the list
  • If you need fields beyond the default set (like lifecyclestage or a custom property), follow up with a Read Contact inside the loop
  • Use a dynamic HubSpot list rather than a static one if you want the workflow to re-evaluate the filter every run
  • If your list is over 5,000 members, split it in HubSpot first using list filters (e.g. date ranges or lead source) so each sub-list stays under the cap

Read Contact

Look up a single HubSpot contact by email address. Useful for enriching workflows that start from an email (form submission, parsed message, webhook payload).

  • Form submission → Read Contact by email → branch on whether they exist
  • Inside a ForEach loop over Read Contact List output, fetch additional properties not in the default set
  • Look up the contact owner or lifecycle stage before deciding which workflow path to take

Returns a flat object with email, firstname, lastname, phone, company, jobtitle, contact_id, and a found flag. found is false when no HubSpot contact matches that email. This is a normal outcome (not an error), so pair it with a Condition step to branch on "did we know this person."

Tips
  • Always check the found field before using the returned properties; they're empty when no contact matched

Read Company

Look up a single HubSpot company by domain. The complement to Read Contact for workflows that start from a domain rather than an email.

  • Parse a domain from a form or scraped page → Read Company → enrich with firmographic data
  • Inside a B2B workflow, look up the company record before deciding which sales rep to assign
  • Check if a domain is already a customer (lifecycle stage) before sending outreach

Returns a flat object with name, domain, industry, numberofemployees, phone, city, country, company_id, and a found flag. Same found semantics as Read Contact: branch on the field rather than treating no-match as an error. HubSpot does NOT enforce domain uniqueness on companies; if your portal has multiple companies with the same domain, this returns the oldest match.

Tips
  • Use the bare root domain (acme.com), no protocol or path
  • If multiple companies in your portal share a domain and you keep updating an unexpected one, clean up duplicates in HubSpot first

Upsert Contact

Create or update a HubSpot contact, keyed by email. One action handles both cases: HubSpot creates a new contact if no match exists, otherwise it updates the existing one.

  • Push enriched leads back to HubSpot after AI scoring
  • Sync form submissions into HubSpot as new or updated contacts
  • B2B workflows: upsert the contact and link it to a company in the same step

Pick the properties you want to write using the property picker. It shows every contact property on your portal, standard and custom alike. Each row pairs a HubSpot property with an expression that resolves to the value (e.g. set firstname to {{form.first_name}}). Empty resolved values are skipped, so you can't accidentally overwrite existing data with a blank. The Linked Records field attaches the contact to a HubSpot company or deal in the same call, typically using a record ID from a sibling step (e.g. {{company_step.company_id}}). Returns the contact ID, the email, and a was_created flag (true on insert, false on update).

Tips
  • Custom properties live in the same picker as standard ones, so write to them the same way
  • For B2B workflows that write both a contact and a company, run Upsert Company FIRST so its ID is available to pass into the contact's Linked Records
  • Branch on the was_created flag with a Condition step if you want to do something different on first insert (welcome email, owner assignment, etc.)

Upsert Company

Create or update a HubSpot company, keyed by domain. Pairs with Upsert Contact for B2B workflows that write both records in the same run.

  • Push enriched company records from AI-driven domain research
  • Sync newly-qualified companies from a sales workflow
  • Update firmographic data after scraping a company website

Same shape as Upsert Contact but keyed by domain. Pick properties from the company picker (name, industry, numberofemployees, plus any custom properties on your portal). The Linked Records field attaches the company to a HubSpot contact or deal in the same call. Returns the company ID, domain, and was_created flag. Caveat: HubSpot doesn't enforce domain uniqueness, so if your portal has duplicate-domain rows the upsert updates the oldest one.

Tips
  • Run before Upsert Contact in B2B workflows so the company_id is available for the contact's Linked Records field
  • Use the bare root domain (acme.com), not a full URL

Create Note

Attach a note to a contact, company, or deal record. The "loop closer" for recording workflow activity on the HubSpot record's activity timeline.

  • Record "Formt sent personalised email at 2026-06-04 14:22" after an outreach workflow runs
  • Log an AI-generated summary of research onto the contact timeline
  • Record that a lead was qualified or scored automatically

Pick what kind of record the note attaches to (Contact, Company, or Deal), provide the HubSpot record ID (usually {{prior_step.contact_id}}), and type the note body (HTML formatting is supported). The note appears on the record's activity timeline in HubSpot, dated to when the step ran.

Tips
  • Use an AI Generate step beforehand to compose the note body. A personalised summary lands well on the timeline.
  • For B2B workflows, drop notes on both the contact AND the company so your sales rep sees activity from either side
Tips
  • When linking a contact and a company in the same workflow, upsert the company FIRST so its company_id can be passed into the contact's associations field

Frequently Asked Questions

What happens if I look up a contact that doesn't exist?
Read Contact returns found: false with empty property values. This is a normal outcome, not an error. Use a Condition step downstream to branch on the found field.
What if my list has more than 5,000 members?
Read Contact List caps at 5,000 members per run. The output's list_total field surfaces HubSpot's real count, so you can see when truncation happened. For larger lists, split them up in HubSpot first using list filters (date ranges, lead source, lifecycle stage) so each sub-list stays under the cap.
Why does Upsert Company update an unexpected row sometimes?
HubSpot doesn't enforce domain uniqueness on companies, so a portal can have multiple rows with the same domain. The upsert (and Read Company) lookups match the oldest one. Clean up duplicate-domain rows in HubSpot if this matters for your workflow.
Do I need to define output fields manually?
No. Each action has a locked output schema with field names that match HubSpot property names exactly. Downstream steps can reference them with {{step.firstname}}, {{step.industry}}, etc.
Can I write to custom properties on my HubSpot portal?
Yes. The properties picker on Upsert Contact and Upsert Company shows every property on your portal, including custom ones. There's no separate handling for standard vs. custom; they all use the same picker, paired with an expression for the value.
How do I link a contact to a company in the same workflow?
Run Upsert Company first, then in Upsert Contact use the Linked Records picker to add an association of type Company with the value {{company_step.company_id}}. HubSpot creates the link in the same call.
Does the workflow consume my HubSpot API quota?
Yes. Each workflow step that calls HubSpot counts against your portal's standard public-app quota (HubSpot's default is 100 requests per 10 seconds and 250k per day). Most workflows use a small fraction of this; large list reads make multiple background requests, scaled with the list size.