Every 15 minutes
Keep contacts in sync between Airtable and HubSpot
Contacts stay the same in Airtable and HubSpot whichever side you edit, with the most recent change winning.
- Runs
- Every 15 minutes
- Apps
- Airtable, HubSpot
- AI
- Not needed
- Access
- read/write both
Send this to your assistant
Create a ClickRuntime automation: every 15 minutes, sync contacts both ways between my Airtable 'Contacts' table and HubSpot, and let the newest change win.
First time? Adding ClickRuntime to your assistant takes about two minutes.
Why use it
Why teams use this automation
Some teams genuinely need contacts in both places. Account managers update phone numbers and job titles in HubSpot, while the events or partnerships team keeps the same people in Airtable with their own fields. Within a month the two disagree, and nobody knows which is right.
This checks both sides every 15 minutes for contacts that changed since the last sync and copies the newer version across. Edit in whichever tool suits you, and the other catches up.
What you get out of it
One version of each contact
Name, email, phone, company and title match in both tools.
Edit wherever you work
Account managers stay in HubSpot and other teams stay in Airtable.
Newest change wins
When both sides changed, the more recent edit is kept, so updates aren't overwritten by stale data.
Only changed contacts move
Each run looks at what changed since the last sync, so it stays quick as your lists grow.
How it works
What happens, step by step
Every 15 minutes, ClickRuntime looks for contacts changed since the last sync in both Airtable and HubSpot.
For each changed contact, it compares when each side was last edited.
The newer version is copied to the other side, and new Airtable contacts are added to HubSpot.
It records the time of this sync, so the next run only looks at newer changes.
Before it runs live
- Connect Airtable
- Connect HubSpot
What you get
Build your own
Need it to work differently?
Your own rules
Conditions, thresholds, filters and exceptions, written the way you'd explain them to a colleague.
Any number of apps
One automation can read from one app and update several others. Over 248 apps are ready, and anything with an API works too.
AI where it helps
Summaries, classification, extraction and drafts use ClickRuntime's built-in AI, with no extra key.
On a schedule or on each event
Runs as often as every 5 minutes, or the moment an app sends an event, and keeps track of what it already handled.
Changes you can ask for
- Run it at a different time, more or less often, or only on weekdays
- Add an AI step, for example to summarize, sort or draft a reply
- Add another app, for example also send it to Slack
- Combine it with other Airtable and HubSpot steps into one automation
Under the hood
The code that runs this automation
import time; from datetime import datetime, timezone
from clickruntime import on_trigger, credentials, http, state
BASE_ID = "appXXXXXXXXXXXXXX"
MAP = {"Email": "email", "First name": "firstname", "Last name": "lastname", "Phone": "phone", "Company": "company", "Title": "jobtitle"}
def hs(method, path, **kw):
r = http.request(method, f"https://api.hubapi.com{path}", headers={"Authorization": f"Bearer {credentials.connection('hubspot', 'work')}"}, **kw)
return r.raise_for_status() or (r.json() if r.content else {})
def at(method, path="", **kw):
r = http.request(method, f"https://api.airtable.com/v0/{BASE_ID}/{path}", headers={"Authorization": f"Bearer {credentials.connection('airtable', 'work')}"}, **kw)
return r.raise_for_status() or r.json()
def to_air(rec_id, h):
at("PATCH", "Contacts", json={"records": [{"id": rec_id, "fields": {k: h["properties"].get(v) or "" for k, v in MAP.items()}}]})
@on_trigger
def run(event):
since_ms, started = state.get("since") or int((time.time() - 3600) * 1000), int(time.time() * 1000)
air = at("GET", "Contacts", params={"filterByFormula": f"IS_AFTER(LAST_MODIFIED_TIME(), '{datetime.fromtimestamp(since_ms / 1000, timezone.utc).isoformat()}')"})["records"]
hub = {c["id"]: c for c in hs("POST", "/crm/v3/objects/contacts/search", json={"filterGroups": [{"filters": [{"propertyName": "lastmodifieddate", "operator": "GTE",
"value": str(since_ms)}]}], "properties": list(MAP.values()) + ["lastmodifieddate"], "limit": 100})["results"]}
for r in air:
f, hid = r["fields"], r["fields"].get("HubSpot ID"); h = hub.pop(hid, None)
if h and h["properties"]["lastmodifieddate"] > f.get("Last modified", ""):
to_air(r["id"], h); continue
props = {v: f.get(k, "") for k, v in MAP.items()}
res = hs("POST", "/crm/v3/objects/contacts/batch/upsert", json={"inputs": [{"idProperty": "email", "id": props["email"], "properties": props}]})["results"][0]
if not hid: at("PATCH", "Contacts", json={"records": [{"id": r["id"], "fields": {"HubSpot ID": res["id"]}}]})
for hid, h in hub.items():
for found in at("GET", "Contacts", params={"filterByFormula": f"{{HubSpot ID}} = '{hid}'"})["records"][:1]:
to_air(found["id"], h)
state.set("since", started)
return {"airtable_changed": len(air)}FAQ
Common questions
Which fields are kept in sync?
How are the two records linked?
Does it delete contacts?
What permissions does it need?
Does it need AI?
How many runs does it use?
Can I change how it works?
Related
You might also like
Turn people in your Airtable engagement table into HubSpot contacts
People added to your Airtable engagement table, such as those who comment on your LinkedIn posts, become HubSpot contacts and are marked as synced.
See how it worksCopy new HubSpot deals into Airtable
Every new HubSpot deal appears in your Airtable Deals table within 15 minutes, with a heads-up in Slack.
See how it worksStart an Airtable onboarding record when a HubSpot deal is won
When a deal is won, an onboarding record is created in your Airtable tracker and the contact becomes a Customer in HubSpot.
See how it worksKeep exploring
Set up “Keep contacts in sync between Airtable and HubSpot” from your assistant
Copy the prompt above, or connect your assistant first. Free plan, no credit card.