Batch enrich HubSpot contacts missing job title or company size using Zapier
Prerequisites
- Zapier account on the Professional plan or higher (required for Webhooks by Zapier, Code by Zapier, and Looping)
- HubSpot account connected to Zapier via OAuth
- Apollo API key with enrichment credits
Why Zapier?
If you're already running Zaps for other HubSpot workflows and want batch enrichment in the same platform, Zapier keeps everything in one place. The Code by Zapier step handles the "only fill empty fields" logic cleanly, and Filters prevent unnecessary downstream steps from burning tasks.
The trade-off is cost. Looping charges tasks for every step in every iteration. A 20-contact batch with 4-5 steps per contact costs 80-100 tasks per run. Weekly runs eat 320-400 tasks/month — nearly half the Professional plan's 750-task allowance. For batches larger than 15-20 contacts, n8n or the Code approach is significantly more economical.
Overview
This Zap runs weekly, searches HubSpot for contacts missing key fields, enriches them via Apollo, and updates HubSpot — writing only to empty fields. Since Zapier doesn't support Apollo natively, you'll use Webhooks by Zapier for all API calls.
HubSpot's "New Contact" trigger in Zapier only fires for newly created contacts. For batch enrichment of existing contacts that are missing data, you need to search via the API using Webhooks by Zapier.
Step 1: Add a weekly Schedule trigger
Create a new Zap. Choose Schedule by Zapier:
- Trigger event: Every Week
- Day of the week: Sunday
- Time of day: 10:00pm
- Timezone: Your team's timezone
Step 2: Search HubSpot for contacts missing fields
Add a Webhooks by Zapier action:
- Action event: Custom Request
- Method: POST
- URL:
https://api.hubapi.com/crm/v3/objects/contacts/search - Headers:
Authorization:Bearer YOUR_HUBSPOT_ACCESS_TOKENContent-Type:application/json
- Data:
{
"filterGroups": [{
"filters": [{
"propertyName": "jobtitle",
"operator": "NOT_HAS_PROPERTY"
}]
}],
"properties": ["email", "firstname", "lastname", "jobtitle", "company", "phone"],
"limit": 20
}Zapier charges tasks per loop iteration per step. A 20-contact batch with 4 steps per iteration = 80 tasks. Keep the limit small (10-20) to avoid burning through your monthly task allowance in a single run.
Step 3: Loop through contacts
Add a Looping by Zapier step:
- Values to loop: Map the
resultsarray from the search response
Zapier will iterate through each contact in the array, running the subsequent steps for each one.
Step 4: Call Apollo for enrichment
Inside the loop, add a Webhooks by Zapier action:
- Method: POST
- URL:
https://api.apollo.io/api/v1/people/match - Headers:
x-api-key: Your Apollo API keyContent-Type:application/json
- Data:
{
"email": "{{contact_email}}"
}Map the email from the current loop iteration.
Step 5: Filter for successful matches
Add a Filter by Zapier step:
- Field:
personfrom the Apollo response - Condition: Exists
This stops processing for contacts Apollo couldn't match.
Step 6: Build the update with Code by Zapier
Add a Code by Zapier step (JavaScript) to only include fields that are currently empty:
const existing = {
jobtitle: inputData.existing_jobtitle || "",
company: inputData.existing_company || "",
phone: inputData.existing_phone || "",
};
const apollo = {
jobtitle: inputData.apollo_title || "",
company: inputData.apollo_company || "",
phone: inputData.apollo_phone || "",
};
const updates = {};
if (!existing.jobtitle && apollo.jobtitle) updates.jobtitle = apollo.jobtitle;
if (!existing.company && apollo.company) updates.company = apollo.company;
if (!existing.phone && apollo.phone) updates.phone = apollo.phone;
return {
hasUpdates: Object.keys(updates).length > 0 ? "true" : "false",
jobtitle: updates.jobtitle || "",
company: updates.company || "",
phone: updates.phone || "",
};Set up Input Data to map:
existing_jobtitle→ contact's current job title from the searchexisting_company→ contact's current companyexisting_phone→ contact's current phoneapollo_title→person__titlefrom Apolloapollo_company→person__organization__namefrom Apolloapollo_phone→person__phone_numbers__sanitized_numberfrom Apollo
Step 7: Filter and update HubSpot
Add a Filter by Zapier step:
- Field:
hasUpdatesfrom the Code step - Condition: Exactly matches
true
Add a HubSpot action:
- Action event: Update Contact
- Contact to update: Map the Contact ID from the loop
- Job Title: Map
jobtitlefrom the Code step (only if non-empty) - Company Name: Map
companyfrom the Code step
When you map an empty string to a HubSpot field in Zapier, it sends an empty string — which overwrites the existing value. Use the Code step to ensure you only map non-empty values, or use Zapier's built-in "Don't send this field if empty" toggle if available.
Step 8: Test and publish
- Test each step to verify the data flow
- Run the full Zap manually and check HubSpot for updates
- Verify that existing data was not overwritten
- Turn the Zap On
Troubleshooting
Cost and task usage
- Zapier Professional: $29.99/mo (750 tasks)
- Per contact: ~4-5 tasks (loop + webhook + filter + code + update). 20 contacts = 80-100 tasks per run.
- Weekly runs: ~320-400 tasks/month. Fits within Professional plan, but leaves limited headroom.
- Apollo: 1 credit per enrichment. 20 contacts/week = 80 credits/month.
- Scale warning: At 50+ contacts/week, you'll likely need the Team plan ($69.99/mo for 2,000 tasks).
Limitations
- No bulk Apollo endpoint — Zapier calls Apollo individually for each contact (no batch support within a Webhook step)
- Task-intensive looping — each loop iteration multiplies task usage across all downstream steps
- No persistent state — Zapier can't remember which contacts were previously enriched without an external store
- Pagination — the search only returns the first page of results (up to the limit). For more than 100 contacts, you'd need additional Webhook steps with the
aftercursor.
Common questions
How many contacts can I enrich per month on the Professional plan?
The Professional plan includes 750 tasks/month. Each contact uses 4-5 tasks through the loop. At 20 contacts/week, that's 320-400 tasks/month — fits within Professional but leaves limited headroom for other Zaps. At 50 contacts/week, you'll need the Team plan ($69.99/mo, 2,000 tasks).
Why can't I use Apollo's bulk_match endpoint in Zapier?
Zapier's Looping processes one contact at a time, so each iteration makes its own API call. There's no way to batch 10 contacts into a single Webhook request within a loop. This means 20 contacts = 20 individual Apollo calls instead of 2 bulk calls. It works, but uses the same credits less efficiently.
Do Filter steps count as tasks?
No. Filter steps that halt execution don't count as tasks in Zapier. This is why the Filter after Apollo (checking that person exists) is free — it prevents the downstream Code and Update steps from running on empty results, saving 2-3 tasks per unmatched contact.
Next steps
- Add a Delay step — insert a 1-second Delay by Zapier between loop iterations to respect Apollo's rate limits
- Track enrichment — add a final Update Contact step to set
enrichment_dateandenrichment_sourceproperties - Add Slack notification — append a Slack step after the loop to summarize the batch results
Looking to scale your AI operations?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.