

How to Bi-directional contact sync with Pipedream
Automatically sync contact updates between Google Sheets and Salesforce in both directions using Pipedream webhooks.
Steps and UI details are based on platform versions at time of writing β check each platform for the latest interface.
Best for
Sales teams who update contacts in both Sheets and Salesforce and need real-time sync without duplicates.
Not ideal for
Teams who need bulk historical sync or can live with one-way sync from Salesforce to Sheets.
Sync type
real-timeUse case type
syncReal-World Example
A 25-person real estate team tracks leads in Google Sheets but closes deals in Salesforce. Agents update phone numbers and addresses in Sheets while reps update deal status and notes in Salesforce. Before automation, data conflicts happened daily and agents wasted 45 minutes manually syncing changes.
What Will This Cost?
Drag the slider to your expected monthly volume.
Each platform counts differently β Zapier: 1 task per trigger. Make: 1 operation per module per record. n8n: 1 execution per run.





Prices shown for annual billing. Based on published pricing as of April 2026.
Estimated ROI
1000
min saved/mo
$583
labor value/mo
Free
no platform cost
Based on ~2 min manual effort per operation at $35/hr fully loaded labor cost.
Implementation
Import this workflow directly into Pipedream
Copy the pre-built Pipedream blueprint and paste it straight into Pipedream. All modules, filters, and field mappings are already configured β you just need to connect your accounts.
Before You Start
Make sure you have everything ready.
Field Mapping
Map these fields between your apps.
| Field | API Name | |
|---|---|---|
| Required | ||
| First Name | FirstName | |
| Last Name | LastName | |
| Email Address | Email | |
| Last Sync Time | Last_Sync__c | |
7 optional fieldsβΈ show
| Phone Number | Phone |
| Company Name | Company__c |
| Job Title | Title |
| Street Address | MailingStreet |
| City | MailingCity |
| State | MailingState |
| Lead Source | LeadSource |
Step-by-Step Setup
Workflows > New > Sources > HTTP / Webhook Requests
Create HTTP trigger for Google Sheets changes
Navigate to pipedream.com and click New Workflow. Select HTTP / Webhook Requests as your trigger source. This creates an instant webhook URL that Google Sheets Apps Script will call when rows change. Copy the webhook URL from the trigger configuration - you'll need this for the Apps Script setup.
- 1Click 'New Workflow' from your Pipedream dashboard
- 2Select 'HTTP / Webhook Requests' from the trigger sources
- 3Name your workflow 'Sheets to Salesforce Sync'
- 4Copy the generated webhook URL
Google Sheets > Extensions > Apps Script
Add Google Sheets Apps Script trigger
Open your Google Sheet and go to Extensions > Apps Script. Create an onEdit trigger that sends a POST request to your Pipedream webhook when cells change. The script needs to identify which row changed and send the contact data as JSON. Test the script by editing a contact row and checking if the webhook receives data.
- 1Click Extensions > Apps Script in your Google Sheet
- 2Replace the default code with the webhook POST function
- 3Set your Pipedream webhook URL in the script
- 4Save and authorize the script permissions
This code prevents infinite sync loops by checking if a contact was recently updated from the opposite system. Paste it as a Code step before each update action in both workflows.
JavaScript β Code Stepexport default defineComponent({βΈ Show code
export default defineComponent({
async run({ steps, $ }) {
const salesforceData = steps.trigger.event;... expand to see full code
export default defineComponent({
async run({ steps, $ }) {
const salesforceData = steps.trigger.event;
const lastSyncTime = salesforceData.Last_Sync__c;
const lastModified = new Date(salesforceData.LastModifiedDate);
const syncThreshold = 5 * 60 * 1000; // 5 minutes
// Skip if recently synced from Sheets
if (lastSyncTime) {
const timeSinceSync = Date.now() - new Date(lastSyncTime).getTime();
if (timeSinceSync < syncThreshold) {
console.log('Skipping sync - recently updated from Sheets');
return $.respond({ skip: true, reason: 'Recent sync detected' });
}
}
// Check for rapid successive updates
const timeSinceModified = Date.now() - lastModified.getTime();
if (timeSinceModified < 30000) { // 30 seconds
console.log('Skipping sync - too soon after last modification');
return $.respond({ skip: true, reason: 'Rate limit applied' });
}
return {
shouldSync: true,
contactId: salesforceData.Id,
syncTimestamp: new Date().toISOString()
};
}
});Workflow Steps > Add Step > Apps > Salesforce
Connect Salesforce account
Add a new step in Pipedream and search for Salesforce. Select 'Update Record' as your action since you'll be updating existing contacts. Connect your Salesforce account through OAuth - this requires admin permissions to create connected apps. Pipedream handles the OAuth flow but you need proper Salesforce API access.
- 1Click the + button below your HTTP trigger
- 2Search for and select 'Salesforce'
- 3Choose 'Update Record' action
- 4Click 'Connect Account' and complete OAuth
Workflow Steps > Add Step > Code
Configure Salesforce contact lookup
Before updating a contact, you need to find it by email address. Add a Code step above the Salesforce update to query for existing contacts. Use the Salesforce REST API to search by email and return the Contact ID. This prevents creating duplicates and ensures you're updating the right record.
- 1Click + to add a step above Salesforce Update
- 2Select 'Run Node.js Code'
- 3Add the contact search code using Salesforce API
- 4Reference the email from steps.trigger.event.body.email
Salesforce Step > Field Mapping
Map Google Sheets fields to Salesforce
Configure the Salesforce Update Record step to map your Google Sheets columns to Contact fields. Use the Contact ID from your code step as the record identifier. Map common fields like FirstName, LastName, Email, Phone, and MailingAddress. Reference the webhook data using steps.trigger.event.body notation.
- 1Set Object Type to 'Contact'
- 2Set Record ID to steps.code.contact_id
- 3Map FirstName to steps.trigger.event.body.first_name
- 4Map additional fields like Email, Phone, Company
Workflows > New > Salesforce > Updated Record
Create second workflow for Salesforce to Sheets
Create a new workflow to handle the reverse sync when Salesforce contacts change. Start with a Salesforce trigger - use 'Updated Record' and select Contact object. This monitors Salesforce for contact changes and fires immediately when fields are modified. Configure it to trigger only on specific fields to avoid infinite loops.
- 1Click 'New Workflow' and name it 'Salesforce to Sheets Sync'
- 2Select Salesforce as the trigger source
- 3Choose 'Updated Record' trigger
- 4Select 'Contact' object and specify field filters
Workflow Steps > Add Step > Google Sheets > Update Spreadsheet Row
Add Google Sheets connection for updates
Add a Google Sheets step to your Salesforce-triggered workflow. Choose 'Update Spreadsheet Row' action and connect your Google account. You'll need to specify the spreadsheet ID, worksheet name, and how to identify which row to update. Use email address as the lookup field since it should match between systems.
- 1Click + to add a Google Sheets step
- 2Select 'Update Spreadsheet Row' action
- 3Connect your Google account via OAuth
- 4Choose your contact spreadsheet and worksheet
Google Sheets Step > Row Configuration
Configure row lookup and field mapping
Set up the Google Sheets step to find the correct row by email address and update the corresponding fields. Configure the lookup column (usually email) and map Salesforce contact fields to your sheet columns. Include a timestamp column to track when the sync occurred from each direction.
- 1Set 'Lookup Column' to your email column name
- 2Set 'Lookup Value' to steps.trigger.event.Email
- 3Map FirstName to your first name column
- 4Add a 'Last Updated' timestamp field
Workflow Steps > Add Step > Code > Loop Prevention
Add duplicate prevention logic
Create a Code step in both workflows to prevent infinite sync loops. Check if the update originated from the same platform by comparing timestamps or adding a sync flag. Skip the sync if the record was recently updated from the opposite direction. This prevents changes from bouncing back and forth endlessly.
- 1Add a Code step before each update action
- 2Compare LastModifiedDate with sync timestamps
- 3Return null to skip sync if recently updated
- 4Log the skip reason for debugging
Workflows > Test > Event History
Test both sync directions
Test the complete bi-directional sync by updating a contact in Google Sheets and verifying it updates in Salesforce. Then update a different field in Salesforce and confirm it syncs back to Sheets. Check the workflow logs for any errors and verify that your loop prevention is working correctly.
- 1Update a phone number in Google Sheets
- 2Check Salesforce for the updated phone number
- 3Update an address in Salesforce
- 4Verify the address appears in Google Sheets
Going live
Production Checklist
Before you turn this on for real, confirm each item.
Troubleshooting
Common errors and how to fix them.
Frequently Asked Questions
Common questions about this workflow.
Analysis
Use Pipedream for this if your team codes and needs real-time bi-directional sync with custom logic. Pipedream's instant webhooks fire within 5 seconds of changes in either system, and the Node.js code steps let you build sophisticated duplicate prevention and field transformation. You can handle complex scenarios like contact merging and data validation that drag-and-drop tools can't touch. Skip Pipedream if your team wants a purely visual interface - Zapier's multi-step Zaps are easier for non-technical users to understand and modify.
Pipedream charges 1 credit per workflow execution. With bi-directional sync, you'll use 2 credits per contact change (one for each direction). At 500 contact updates per month, that's 1000 credits or $10 on the paid plan. Zapier costs $20/month for the same volume since each direction counts as a separate Zap. Make.com is cheaper at $9/month but maxes out at 1000 operations total, so you'll hit limits faster with complex lookup logic.
Make.com handles the visual mapping better with its scenario builder showing exactly how data flows between systems. Zapier's filter and formatter steps are more intuitive for field transformation than writing JavaScript code. N8n offers similar coding flexibility to Pipedream but requires self-hosting for the free tier. Power Automate integrates deeply with Microsoft 365 if you're already in that ecosystem. But Pipedream's instant webhook processing beats all competitors for real-time sync speed, and the built-in data stores let you cache contact mappings without external databases.
You'll hit Salesforce API rate limits if your team makes lots of rapid contact changes - the standard limit is 5000 calls per hour. Google Sheets has a 100 requests per 100 seconds limit that can break during bulk operations. Both systems occasionally return temporary errors that require retry logic. The biggest gotcha is timestamp sync conflicts when users update contacts simultaneously in both systems - build in conflict detection or you'll get data overwrites without warning.
Ideas for what to build next
- βAdd lead scoring sync β Sync lead scores from Salesforce to a score column in Google Sheets for team visibility and prioritization.
- βInclude opportunity data β Extend the sync to include related opportunity counts and values from Salesforce for complete contact context.
- βSet up sync reporting β Create a dashboard in Google Sheets showing sync success rates, errors, and data quality metrics from both systems.
Related guides
How to Share Notion Meeting Notes to Slack with Pipedream
~15 min setup
How to Share Notion Meeting Notes to Slack with Power Automate
~15 min setup
How to Share Notion Meeting Notes to Slack with n8n
~20 min setup
How to Send Notion Meeting Notes to Slack with Zapier
~8 min setup
How to Share Notion Meeting Notes to Slack with Make
~12 min setup
How to Create Notion Tasks from Slack with Pipedream
~15 min setup