Skip to content
Docs
flero.ai

Recipe 02, Scheduled data sync (HubSpot → Salesforce)

Goal: every hour, fetch HubSpot contacts created in the last hour and upsert them as Salesforce leads.

Difficulty: Intermediate · Time: ~25 minutes · Connectors: HubSpot, Salesforce


Prerequisites

  • HubSpot credential with read access to Contacts.
  • Salesforce credential with write access to Leads.
  • A test contact in HubSpot you can use to verify.

Finished workflow

┌──────────┐   ┌─────────────────────┐   ┌──────┐   ┌───────────┐   ┌────────────────────┐
│ Schedule │──▶│ HubSpot: list       │──▶│ Loop │──▶│ Transform │──▶│ Salesforce: upsert │
└──────────┘   │   contacts since    │   └──┬───┘   └───────────┘   │       Lead         │
               │   last sync         │      │ done                  └────────────────────┘
               └─────────────────────┘      ▼
                                       ┌─────────┐
                                       │ Set:    │
                                       │ lastSync│
                                       └─────────┘

Step-by-step

1. Create the workflow

/workflowsCreate workflowHubSpot → Salesforce hourly sync.

2. Configure the schedule

Replace Start with a Schedule Trigger:

  • Type: Cron
  • Expression: 0 * * * * (top of every hour)
  • Time zone: your workspace's

3. List HubSpot contacts since last sync

Add HubSpot node:

  • Credential: your HubSpot credential
  • Operation: List contacts
  • Filter: createdAt > {{ $workflow.variables.lastSync }}
  • Sort: createdAt asc
  • Limit: 100

For the first run, $workflow.variables.lastSync will be empty. Add a small "first-run" fallback:

Drag a Set node before the HubSpot node:

  • Name: defaultLastSync
  • Value: {{ $workflow.variables.lastSync || $trigger.previousScheduledTime }}
  • Scope: run

Then update the HubSpot filter to: createdAt > {{ $node["defaultLastSync"].json.value }}.

4. Loop over the results

Add a Loop node connected to HubSpot's output:

  • Mode: array
  • Source: {{ $node["HubSpot"].json.results }}

5. Transform each contact

In the Loop's body, add a Transform node:

map:
  Email:      {{ $input.email }}
  FirstName:  {{ $input.firstName }}
  LastName:   {{ $input.lastName }}
  Company:    {{ $input.company }}
  LeadSource: HubSpot

6. Upsert as Salesforce lead

Add Salesforce node after Transform:

  • Credential: Salesforce
  • Operation: Upsert Lead
  • External ID field: Email (so duplicate emails don't create duplicate leads)
  • Fields: pull from Transform output

7. Remember the last sync time

After the Loop's done port, add a Set node:

  • Name: lastSync
  • Value: {{ $trigger.scheduledTime }}
  • Scope: workflow (persists across runs)

8. Save and activate

Save. Change status to Active. The first run happens at the next hour boundary.


Try it

Manually trigger the first run from the editor:

  1. Click Run in the toolbar.
  2. Watch the canvas, nodes turn green in sequence.
  3. Open Salesforce, verify the leads appear.
  4. Switch back; click into the execution detail.
  5. The lastSync variable should now be populated. Next run will only fetch contacts created since.

Variations

Run only on weekdays

Cron: 0 * * * 1-5 (every hour, Mon–Fri).

Batch process with concurrency

Replace the Loop with a Distributed Execution node to run upserts in parallel across workers. Faster for large syncs.

Notify on failure

Add an Error Handling node around the Salesforce upsert. On error, route to a Slack node that posts to your ops channel.

Add a digest summary

After the Loop's done port, send an email summarising the run: count of contacts synced, count of failures.


Troubleshooting

Symptom Likely cause Fix
First run fetches no contacts lastSync is empty; filter excludes all Use the defaultLastSync fallback as described
Duplicate leads in Salesforce Not using upsert; or external ID field misconfigured Switch operation to Upsert Lead with Email as the external ID
Salesforce rejects with "field required" Lead has required fields beyond what HubSpot supplied Add them in the Transform step with sensible defaults
Workflow times out on big batches Default 5-min timeout Workflow settings → Execution timeout: bump to 30 min, or switch to Distributed Execution

Next


Found something out of date? This page lives in the Flero docs content set.