Intermediate~15 min setupProductivity & CRMVerified April 2026
Google Sheets logo
Salesforce logo

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-time

Use case type

sync

Real-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.

/mo
505005K50K

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

Skip the setup

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.

Google Sheets with contact data including email addresses as unique identifiers
Salesforce org with API access enabled and Contact records matching sheet emails
Salesforce user account with modify permissions on Contact object and custom fields
Google account with edit access to the target spreadsheet for Apps Script installation

Field Mapping

Map these fields between your apps.

FieldAPI Name
Required
First NameFirstName
Last NameLastName
Email AddressEmail
Last Sync TimeLast_Sync__c
7 optional fieldsβ–Έ show
Phone NumberPhone
Company NameCompany__c
Job TitleTitle
Street AddressMailingStreet
CityMailingCity
StateMailingState
Lead SourceLeadSource

Step-by-Step Setup

1

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.

  1. 1Click 'New Workflow' from your Pipedream dashboard
  2. 2Select 'HTTP / Webhook Requests' from the trigger sources
  3. 3Name your workflow 'Sheets to Salesforce Sync'
  4. 4Copy the generated webhook URL
βœ“ What you should see: You should see a unique webhook URL starting with https://eoxxx.m.pipedream.net ready to receive POST requests.
⚠
Common mistake β€” The webhook URL is public by default. Add HTTP Basic Auth in the trigger settings if your data is sensitive.
Pipedream
+
click +
search apps
Google Sheets
GO
Google Sheets
Create HTTP trigger for Goog…
Google Sheets
GO
module added
2

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.

  1. 1Click Extensions > Apps Script in your Google Sheet
  2. 2Replace the default code with the webhook POST function
  3. 3Set your Pipedream webhook URL in the script
  4. 4Save and authorize the script permissions
βœ“ What you should see: The Apps Script editor shows your webhook function saved with green checkmark. Test by editing a row - you should see a POST request in Pipedream logs.
⚠
Common mistake β€” Apps Script onEdit triggers fire on ANY cell change. Add row/column filters to avoid sending non-contact updates.

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()
    };
  }
});
3

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.

  1. 1Click the + button below your HTTP trigger
  2. 2Search for and select 'Salesforce'
  3. 3Choose 'Update Record' action
  4. 4Click 'Connect Account' and complete OAuth
βœ“ What you should see: Salesforce appears as connected with a green badge. The Update Record action shows dropdown menus for Object Type and fields.
⚠
Common mistake β€” Salesforce sandboxes use different OAuth endpoints. Switch to Production login if you're connecting to live Salesforce.
Pipedream settings
Connection
Choose a connection…Add
click Add
Google Sheets
Log in to authorize
Authorize Pipedream
popup window
βœ“
Connected
green checkmark
4

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.

  1. 1Click + to add a step above Salesforce Update
  2. 2Select 'Run Node.js Code'
  3. 3Add the contact search code using Salesforce API
  4. 4Reference the email from steps.trigger.event.body.email
βœ“ What you should see: The code step shows your contact search function. Test with a known email - it should return a Contact ID or null if not found.
⚠
Common mistake β€” SOQL queries are case-sensitive for exact matches. Use LIKE operator with LOWER() for fuzzy email matching.
5

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.

  1. 1Set Object Type to 'Contact'
  2. 2Set Record ID to steps.code.contact_id
  3. 3Map FirstName to steps.trigger.event.body.first_name
  4. 4Map additional fields like Email, Phone, Company
βœ“ What you should see: Field mapping shows your Google Sheets data flowing to Salesforce Contact fields. All required fields have values mapped.
⚠
Common mistake β€” Salesforce requires either Contact ID or external ID for updates. Don't use email as the identifier - it's not guaranteed unique.
Google Sheets fields
Column A
Column B
Email
Status
Notes
available as variables:
1.props.Column A
1.props.Column B
1.props.Email
1.props.Status
1.props.Notes
6

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.

  1. 1Click 'New Workflow' and name it 'Salesforce to Sheets Sync'
  2. 2Select Salesforce as the trigger source
  3. 3Choose 'Updated Record' trigger
  4. 4Select 'Contact' object and specify field filters
βœ“ What you should see: Salesforce trigger shows 'Contact' selected with field filters active. It should display recent contact updates in the test data.
⚠
Common mistake β€” Salesforce triggers fire on every field update. Filter to specific fields or you'll sync system fields like LastModifiedDate.
7

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.

  1. 1Click + to add a Google Sheets step
  2. 2Select 'Update Spreadsheet Row' action
  3. 3Connect your Google account via OAuth
  4. 4Choose your contact spreadsheet and worksheet
βœ“ What you should see: Google Sheets step shows your spreadsheet connected with worksheet tabs visible. Row update settings show column mapping options.
⚠
Common mistake β€” Google Sheets API has different column naming than the UI. Column A = col1, not 'A'. Check the API reference for exact names.
8

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.

  1. 1Set 'Lookup Column' to your email column name
  2. 2Set 'Lookup Value' to steps.trigger.event.Email
  3. 3Map FirstName to your first name column
  4. 4Add a 'Last Updated' timestamp field
βœ“ What you should see: Field mapping shows Salesforce contact data flowing to Google Sheets columns. Lookup configuration shows email as the matching field.
⚠
Common mistake β€” Google Sheets row lookup is case-sensitive. Normalize email addresses to lowercase in both directions to prevent missed matches.
9

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.

  1. 1Add a Code step before each update action
  2. 2Compare LastModifiedDate with sync timestamps
  3. 3Return null to skip sync if recently updated
  4. 4Log the skip reason for debugging
βœ“ What you should see: Code steps show loop prevention logic active. Test by updating the same record in both systems - only the first change should sync.
⚠
Common mistake β€” Time zone differences can break timestamp comparison. Convert all dates to UTC before comparing modification times.
10

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.

  1. 1Update a phone number in Google Sheets
  2. 2Check Salesforce for the updated phone number
  3. 3Update an address in Salesforce
  4. 4Verify the address appears in Google Sheets
βœ“ What you should see: Both workflows show successful execution logs. Contact data matches between Google Sheets and Salesforce with proper timestamps.
⚠
Common mistake β€” Salesforce validation rules can block updates from Pipedream. Check field requirements and picklist values if updates fail silently.
Pipedream
β–Ά Deploy & test
executed
βœ“
Google Sheets
βœ“
Salesforce
Salesforce
πŸ”” notification
received

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

VerdictWhy n8n for this workflow

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.

Cost

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.

Tradeoffs

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

Was this guide helpful?
← Google Sheets + Salesforce overviewPipedream profile β†’