Skip to main content
This is the v3 version of this guide. The previous version used HubSpot’s Send a webhook action with the v2 Person API. v3 needs a POST with a nested JSON body, which that action can’t build, so this guide uses a Custom code action instead. Existing v2 workflows keep running until November 2026, when v2 endpoints stop responding - migrate before then.
When a Contact is created, this workflow sends whatever identifiers are available to Lusha, receives the matching contact profile, and writes the returned data back onto the record. It uses one Custom code action to call Lusha, then one Edit record action per field you want to store. The code returns 31 values, so you can map all of them, a handful, or just one, and add more later without touching the code.

Prerequisites

1. Check your HubSpot subscription

Custom code actions require Data Hub Professional or Enterprise, which HubSpot previously called Operations Hub. If your portal is on Marketing Hub or Sales Hub only, the Custom code action won’t appear in the action list, and you’ll need a connector platform such as n8n, Workato, Make or Zapier instead. You also need Edit permissions for workflows and Publish permissions to turn it on - not Super Admin, and no app.

2. Get your Lusha API key

Go to Manage API keys and click + Create new Key. Copy it - you’ll paste it once, below. Auth is via an api_key HTTP header, not Bearer.
Who can see the API key? Every Lusha user can create and view their own API key. If the API Keys page is missing, ask your Admin whether API access is turned off for your user.

Custom properties to create

Create a property for each value you want to keep. Everything here is optional, so start with the handful your team will actually use and add more later. See Appendix B for the full list. Go to Settings → Properties → Create property, set the object to Contact, and choose the field type shown in Appendix B.
Write to Lusha properties, not over your own data. Mapping Lusha’s values into dedicated Lusha ... properties keeps your original CRM data intact. An Edit record action set to Replace writes whatever it’s given, including an empty value, so pointing it at a standard property such as Email means an unmatched or partial result can clear data your team entered. Once you trust the match quality, you can add a second set of Edit record actions to copy specific Lusha properties into the standard fields - see Appendix C.

Workflow configuration

There are four core parts to configure:
1

Create the workflow

  1. In HubSpot, go to Automation → Workflows
  2. Click Create workflow → From scratch
  3. Choose Contact-based, then click Next
  4. Name it something descriptive, such as Lusha Contact Enrichment
2

Set the enrollment trigger

  1. Click Set up triggers
  2. Choose When filter criteria is met → Contact properties
  3. Select Create date → is known
  4. Click Apply filter, then Save
Leave re-enrollment turned off. Re-enrollment pushes the same Contact through the workflow again whenever the trigger property changes, and every pass spends credits. Keep it off unless you specifically want to re-enrich. If your portal offers a Contact created object event trigger, use that instead - it produces the same result and reads more clearly to the next admin.
3

Add the Custom code action

  1. Click the + icon below the trigger
  2. In the left panel, search for and select Custom code
  3. Leave Language set to Node.js
  4. In Description, enter something like Call Lusha v3 and return contact data
4

Add your Lusha API key as a secret

Secrets become environment variables, so the key never appears in the code or in test output.
  1. Click Add secret → Create new secret
  2. Secret name: LUSHA_API_KEY
  3. Secret value: your Lusha API key
  4. Click Save, then make sure the checkbox next to the secret is ticked
The name has to match the code exactly. The code reads process.env.LUSHA_API_KEY. A secret named differently reads as undefined, and the call fails with a 401 that looks like an invalid key.
5

Add the input properties

These are the Contact values the code sends to Lusha. Under Properties to include in code, add each one, then set the property name on the right to match the code.
Why send all five? Lusha resolves against the strongest identifier present, and the code only includes the ones that have a value, so sending all five raises your match rate at no extra cost. Some Contacts have an email and nothing else; some have a name and a company with no email. Each property can only be added once, and you can use up to 50 properties in a single Custom code action.
6

Paste the code

Clear the sample code in the Code editor and paste the full script from Appendix A.
What the code does. It reads your five input properties, builds a v3 request body containing only the identifiers that have values, and posts it to https://api.lusha.com/v3/contacts/search-and-enrich. It then pulls 31 values out of the response and returns them as data outputs. It doesn’t write anything to HubSpot itself, so nothing changes on the record until your Edit record actions run. Every output is declared with a default at the top of the script, so the action always returns the complete set - an output missing from a response fails any action that references it, which is why none are left undefined.
7

Define the data outputs

Under Data outputs, click Add output once per row. The names must match the code exactly, including capitalization. See Appendix B for the full table with suggested properties.
You only need to declare the outputs you plan to use. The code returns all 31 regardless. HubSpot flags any output the code produced that you haven’t declared, and that flag is informational, not an error. Declaring all of them now is still the easier path, because adding a new field later becomes a two-minute job in the workflow rather than a code change.
8

Add a branch so unmatched Contacts are left alone

  1. Click the + icon below the Custom code action
  2. Select If/then branch
  3. Branch on Action outputs → your Custom code action → lushaMatched
  4. Set the condition to is equal to → true
  5. Every Edit record action goes on the Yes path. Leave the No path empty
This branch is doing real work. When Lusha has no match, every output comes back empty. An Edit record action set to Replace would then write those empty values over whatever is already on the Contact. The branch means an unmatched Contact passes through untouched. Without it, running this workflow across a database with a low match rate clears data rather than adding it.
9

Add your first Edit record action

This is the pattern you’ll repeat once per field. Email is used here as the example.
  1. On the Yes path, click the + icon
  2. Select Edit record
  3. Record to edit: Contact from trigger
  4. Property to edit: Lusha Work Email
  5. Change type: Replace
  6. Choose a value: click the field, switch the source to your Custom code action, and select lushaWorkEmail
  7. Click Save
Can’t find the output in the value picker? Check that the output is declared under Data outputs in the Custom code action, and that you’ve saved that action since adding it. Declaring an output in the code alone isn’t enough - HubSpot only offers what’s listed in that section. The names are case sensitive: lushaWorkEmail in the code and lushaWorkEmail in Data outputs have to match exactly.
10

Add an Edit record action for each remaining field

Repeat step 9 for every value you want to keep, using the property and output pairs in Appendix B. Each action handles one property, so chain them one after another down the Yes path.
Start small. You don’t have to map all 31. Pick the fields your team will act on, get the workflow live, and add more actions later. Adding one is a two-minute job and needs no code change. The outputs you don’t map cost nothing - they’re returned by the code and simply go unused.
11

Set a rate limit

  1. Click the Custom code action, then Configure rate limit at the bottom
  2. Toggle Turn on rate limiting on
  3. Action executions: 20, Time frame: Seconds
Why 20 per second? Lusha allows up to 25 requests per second across all endpoints, shared by every integration on your account. Every Contact created fires its own execution, so a list import will exceed that and start returning 429 errors. Setting 20 leaves headroom for anything else on your account calling the API. The rate limit also applies to every action after the Custom code action in this workflow.
Your plan also has per-minute, hourly, and daily caps, and these are usually what you hit first. For example, the Pro plan allows 50 requests per minute, so 20 per second uses it up in under 3 seconds. Set the HubSpot limit to fit your plan’s per-minute cap instead - for Pro, 50 executions per Minute at most. Check the x-rate-limit-minute header on any Lusha response, or see plan rate limits, for your exact limits.

Testing and activation

12. Test the action

  1. At the bottom of the Custom code action, click Test action
  2. Click the Contact dropdown and select a record to test with
  3. Click Test, then confirm
  4. When the run finishes, check that Status reads success, that Data outputs shows lushaMatched as true with the fields populated, and that Logs shows no errors
Testing runs the code for real. A HubSpot custom code test isn’t a simulation. The code executes against the Contact you select. It doesn’t write any properties in this version, because the Edit record actions aren’t part of the test, but it does spend a Lusha credit. Test with a person you’ve confirmed in Lusha first, so a lushaMatched value of false tells you something about your configuration rather than about the data.

13. Review and publish

  1. Click Review and publish in the top right
  2. Check the enrollment summary, then click Turn on
At this point the workflow is live. Create a Contact manually to confirm the end-to-end path, then watch the first bulk import closely to make sure the rate limit is holding.
Watch your credit consumption for the first week. Each matched Contact is charged one search plus one credit per revealed field. Contacts that return no match are still charged for the search, so a low match rate costs you credits with nothing to show for it. Map lushaCreditsCharged to a property and report on it. If the no-match rate is high, tighten the enrollment trigger so only Contacts carrying a usable identifier enter the workflow.

Appendix A - full custom code

Paste this into the Code editor of the Custom code action, replacing the sample code HubSpot provides.

Appendix B - output and property reference

Every value the code returns, with a suggested HubSpot property and field type. Create only the properties you want.
Field types in HubSpot. Create String outputs as Single-line text, except lushaAllEmails, lushaAllPhones and lushaPreviousEmployers, which can hold several values and are better as Multi-line text. Boolean outputs map to a Single checkbox. lushaCreditsCharged maps to a Number property. Keep lushaUpdateDate as Single-line text - Lusha returns it as 2026-08-13, and a HubSpot Date property expects a different format, so mapping it to a date type fails the action.
Don’t filter phones on a type of direct. The published documentation lists the phones.type values as mobile, direct, work and unknown. Live responses also return phone, which appears in no version of that list. Filtering for direct silently discards those numbers. The code takes any number that isn’t flagged do not call, prefers a mobile for lushaMobilePhone, and puts the next one in lushaOtherPhone. That keeps it working whatever Lusha labels the rest.

Appendix C - copying Lusha values into standard properties

The main guide writes everything into Lusha ... properties so your existing CRM data is never overwritten. Once you trust the match quality, you can copy specific values into the standard fields. The safe pattern is to write only when the standard field is empty:
  1. On the Yes path, add an If/then branch
  2. Branch on Contact properties → Email → is unknown
  3. On that branch’s Yes path, add an Edit record action
  4. Property to edit: Email, Change type: Replace, Value: lushaWorkEmail
Repeat per field. The pairs most customers use:

Appendix D - writing properties from the code instead

If you’d rather skip the Edit record actions entirely, the code can write the properties itself through HubSpot’s API. This replaces every action on the Yes path with a few lines of code.Weigh it up before you choose it. Writing from code lets you leave a property out of the update when Lusha returns nothing, which is cleaner than writing an empty value. But it needs more setup, more permissions, and ongoing maintenance.

Create the private app

  1. In HubSpot, go to Development → Legacy apps
  2. Click Create legacy app, then select Private
  3. Name it on the Basic Info tab, then open the Scopes tab
  4. Click Add new scope and enable crm.objects.contacts.read and crm.objects.contacts.write, then click Update
  5. Click Create app, then Continue creating
  6. Open the Auth tab, click Show token, and copy it
Private apps now live under Legacy apps. HubSpot moved private apps from Settings into Development → Legacy apps, and labels anything not built on the projects platform as legacy. Legacy private apps are still fully supported and actively maintained, and HubSpot’s own guidance is that a private app is the right choice when you only need an access token for a few APIs. If the user who created the private app is later removed from the account, calls using its token start failing - create it under an account that will outlast the person who set it up.

Change the code

Add the client at the top, then replace the final callback with a property write. Only include the properties you’ve created, because an unrecognised internal name fails the entire update.
Add the token as a second secret named HUBSPOT_TOKEN, and tick it alongside the Lusha key.

Appendix E - troubleshooting

Your portal isn’t on Data Hub Professional or Enterprise. The Webhook action requires the same subscription, so there’s no native alternative. Use a connector platform instead.
Check the secret name matches LUSHA_API_KEY exactly, and that its checkbox is ticked in the action. An unticked secret reads as undefined at runtime. If both look right, test the key outside HubSpot to rule out a revoked or regenerated key.
The output isn’t declared under Data outputs, or the Custom code action hasn’t been saved since you added it. Declaring an output in the code alone isn’t enough. Names are case sensitive.
An Edit record action set to Replace wrote an empty value. Confirm the If/then branch on lushaMatched is in place and that every Edit record action sits on its Yes path. If the Contact did match but one field came back empty, that field genuinely has no value in Lusha, so remove its action or point it at a Lusha property rather than a standard one.
Custom code actions must finish within 20 seconds and are capped at 128 MB. The axios timeout is set to 15 seconds so the Lusha call fails cleanly inside that budget. If you add more API calls, lower that timeout further.
A 429 from Lusha means your rate limit is too high or turned off. Set the Custom code action to 20 executions per second, or lower if your plan’s per-minute cap is smaller (for example, 50 per minute on Pro). Throwing the error in the catch block, as the code does, is what tells HubSpot to retry, so don’t remove it.
Check the enrollment trigger and the workflow’s enrollment history. A trigger of Create date is known only fires for newly created Contacts, so existing records won’t enter unless you enroll them manually.

Additional resources

If you have questions on the above, reach out to the support team:
Built and verified against Lusha API v3, October 2026.