Skip to Content

Set up bidirectional sync between two systems in Jitterbit Studio

Introduction

Bidirectional sync keeps records consistent across two separate systems by propagating changes in either direction: updates made in system A are written to system B, and updates made in system B are written back to system A.

The core challenge is preventing infinite loops. When Studio writes a record to system B, system B's change-detection sees that record as a new modification and (without a safeguard) triggers the B-to-A sync, which writes the record back to system A, which then triggers the A-to-B sync again. A sync flag field on each system's records is the most reliable way to break this cycle: each sync direction marks the records it writes as "synced by Jitterbit," and the opposite direction filters those records out of its queries.

This guide uses Salesforce and NetSuite as the two example systems and assumes familiarity with configuring Studio connections, transformations, and scripts. For background on related topics, see:

Design pattern

The pattern uses two independent operation chains:

flowchart LR A[Salesforce Query] --> B[Transformation] --> C[NetSuite Upsert] --> D[Script] E[NetSuite Search] --> F[Transformation] --> G[Salesforce Upsert] --> H[Script]

Two custom boolean fields carry sync state, one in each external system:

  • Salesforce: Jitterbit_Synced__c: false when a user last modified the record; true when Studio last wrote the record.
  • NetSuite: custbody_jb_synced: the same semantics.

Each system also requires an automation rule that resets the field to false whenever a user modifies a record. This ensures user-initiated changes are always picked up by the next sync run, while records written by Studio are ignored.

How the loop is prevented:

  1. A user modifies an account in Salesforce. The automation rule sets Jitterbit_Synced__c = false.
  2. The Salesforce-to-NetSuite chain queries for records where Jitterbit_Synced__c = false and finds the modified record.
  3. Studio writes the record to NetSuite with custbody_jb_synced = true, then sets Jitterbit_Synced__c = true back in Salesforce.
  4. The NetSuite-to-Salesforce chain queries NetSuite for records where custbody_jb_synced = false. The record from step 3 is excluded, so it is not written back to Salesforce.

Prerequisites

Before configuring the Studio operations, complete the following steps in each external system.

In Salesforce

  1. On the Account object (or whichever object you are syncing), create a custom checkbox field with label Jitterbit Synced and API name Jitterbit_Synced__c. Set the default value to Unchecked.

  2. Create a workflow rule or Process Builder flow that fires on record creation or update by a user and sets Jitterbit_Synced__c to false. This ensures that user-initiated changes are visible to the Salesforce-to-NetSuite chain.

In NetSuite

  1. On the Customer record type (or whichever record type you are syncing), create a custom checkbox field with label Jitterbit Synced and ID custbody_jb_synced. Set the default value to unchecked.

  2. Create a user event SuiteScript (afterSubmit) that fires on record save and sets custbody_jb_synced = false. Use context.type to skip execution when the record is saved by a script (such as a Jitterbit sync write), so only user saves reset the flag.

In your Jitterbit project

Create two project variables to track the last successful sync time for each direction:

  • sf_last_sync_time: the last time the Salesforce-to-NetSuite chain ran successfully. Set an initial value of a date in the past, for example 2020-01-01T00:00:00Z.
  • ns_last_sync_time: the last time the NetSuite-to-Salesforce chain ran successfully. Set the same initial value.

Part 1: Configure the Salesforce-to-NetSuite operation chain

Step 1: Configure the Salesforce Query activity

  1. On the design canvas, drag a Salesforce Query activity from an existing Salesforce endpoint onto the canvas to start the chain.

  2. Double-click the activity to open its configuration.

  3. Name: Enter Query Salesforce Accounts.

  4. Object: Select Account.

  5. Query conditions: Add two conditions:

    • Jitterbit_Synced__c equals false
    • LastModifiedDate greater than $sf_last_sync_time

    Together these conditions return only records modified by users since the last sync run.

  6. Select the fields to return: Id, Name, and any other fields that map to NetSuite.

  7. Click Finished.

Step 2: Configure the transformation

  1. Add a Transformation step after the Salesforce Query activity.

  2. Set the source schema to the Salesforce Query response and the target schema to the NetSuite Customer record structure.

  3. Map the Salesforce source fields to the corresponding NetSuite target fields.

  4. Add a script at the root level (outside the looping node) that marks each written record as "synced by Jitterbit" and captures the current run time:

    target.custbody_jb_synced = true;
    $sync_run_time = Now();
    

    Setting custbody_jb_synced = true on every NetSuite record written by this chain is what prevents the NetSuite-to-Salesforce chain from picking them up and writing them back.

  5. In the same root-level script, append each source record's ID to a global variable so the post-sync script can update Salesforce:

    $synced_sf_ids = $synced_sf_ids + If(Length($synced_sf_ids) > 0, ",", "") + Source.Id;
    

Step 3: Configure the NetSuite Upsert activity

  1. Add a NetSuite Upsert activity after the transformation.

  2. Name: Enter Upsert NetSuite Customers.

  3. Record type: Select Customer.

  4. External ID field: Select the field that stores the Salesforce record ID in NetSuite (for example, custentity_sf_id). This field is the upsert key: if a matching record exists it is updated; otherwise a new record is created.

  5. Click Finished.

Step 4: Write the post-sync script

After upserting to NetSuite, update Salesforce to mark the same records as processed. This prevents them from being re-queried on the next run.

  1. Add a Script step after the NetSuite Upsert activity.

  2. Enter the following:

    // Mark the synced Salesforce records so the next run skips them.
    // $synced_sf_ids is a comma-separated list of Salesforce IDs built in the transformation.
    RunOperation("<TAG>operation:Mark Salesforce Records Synced</TAG>");
    
    // Advance the last-sync timestamp so the next query picks up only newer changes.
    $sf_last_sync_time = $sync_run_time;
    SetVar("sf_last_sync_time", $sf_last_sync_time);
    
    // Reset for the next run.
    $synced_sf_ids = "";
    
  3. Create a separate operation named Mark Salesforce Records Synced containing a Salesforce Update activity. Configure the activity to set Jitterbit_Synced__c = true on each record ID in synced_sf_ids. Use a transformation that splits synced_sf_ids on commas and maps each ID to an update payload.

  4. Also initialize $synced_sf_ids = "" at the start of any controller script that calls this chain, so the ID list is empty at the beginning of each run.

Part 2: Configure the NetSuite-to-Salesforce operation chain

The NetSuite-to-Salesforce chain follows the same structure as Part 1 with the source and target systems swapped.

Step 1: Configure the NetSuite Search activity

  1. Drag a NetSuite Search activity onto the canvas.

  2. Name: Enter Search NetSuite Customers.

  3. Search filter: Add two conditions:

    • custbody_jb_synced is false
    • lastmodifieddate after $ns_last_sync_time
  4. Return fields: Select internalId, entityId, custentity_sf_id, and any other fields that map to Salesforce.

  5. Click Finished.

Step 2: Configure the transformation

  1. Add a Transformation step after the NetSuite Search activity.

  2. Set the source schema to the NetSuite Search response and the target schema to the Salesforce Account structure.

  3. Map the NetSuite source fields to the corresponding Salesforce target fields.

  4. Add a root-level script that marks each Salesforce record as "synced by Jitterbit" and collects NetSuite IDs for the post-sync update:

    target.Jitterbit_Synced__c = true;
    $sync_run_time = Now();
    $synced_ns_ids = $synced_ns_ids + If(Length($synced_ns_ids) > 0, ",", "") + Source.internalId;
    

Step 3: Configure the Salesforce Upsert activity

  1. Add a Salesforce Upsert activity after the transformation.

  2. Name: Enter Upsert Salesforce Accounts.

  3. External ID field: Select the field that stores the NetSuite internal ID in Salesforce (for example, NetSuite_ID__c).

  4. Click Finished.

Step 4: Write the post-sync script

  1. Add a Script step after the Salesforce Upsert activity.

  2. Enter the following:

    RunOperation("<TAG>operation:Mark NetSuite Records Synced</TAG>");
    
    $ns_last_sync_time = $sync_run_time;
    SetVar("ns_last_sync_time", $ns_last_sync_time);
    
    $synced_ns_ids = "";
    
  3. Create a separate operation named Mark NetSuite Records Synced containing a NetSuite Update activity that sets custbody_jb_synced = true on each record ID in synced_ns_ids.

Part 3: Schedule the operations

Both chains run on operation schedules. To reduce the chance of both chains running simultaneously and picking up each other's in-progress writes, stagger their start times.

  1. Assign a schedule to the Salesforce-to-NetSuite chain. For a 15-minute sync interval, set it to run at 0, 15, 30, and 45 minutes past the hour.

  2. Assign a schedule to the NetSuite-to-Salesforce chain and offset it by half the interval (for example, 7, 22, 37, and 52 minutes past the hour).

For step-by-step instructions on creating and assigning schedules, see Schedule an operation to run automatically.

Note

Staggering reduces overlap but does not eliminate it. If a sync run takes longer than the offset interval, both chains may run concurrently. For high-volume syncs, consider adding a lock: at the start of each chain, read a project variable or Cloud Datastore entry named sync_lock. If the lock is set, exit early using RaiseError. Otherwise, set the lock before proceeding and clear it in the post-sync script.

Verify the integration

  1. Deploy and run the Salesforce-to-NetSuite chain manually.

  2. In the operation logs, confirm that records were retrieved from Salesforce and written to NetSuite.

  3. In NetSuite, open one of the synced customer records and confirm that custbody_jb_synced is checked.

  4. In Salesforce, open the corresponding account and confirm that Jitterbit_Synced__c is checked.

  5. Run the NetSuite-to-Salesforce chain. In the logs, confirm that the Customer records written in step 2 do not appear in the NetSuite search results. This confirms that the loop-prevention filter is working: records with custbody_jb_synced = true are excluded.

  6. In NetSuite, modify one of the synced customer records directly. The afterSubmit SuiteScript resets custbody_jb_synced = false. Run the NetSuite-to-Salesforce chain and confirm that the change propagates to Salesforce.

  7. In Salesforce, confirm the updated account has Jitterbit_Synced__c = true after the sync. Run the Salesforce-to-NetSuite chain and confirm the record does not appear in the query results, since it was last written by Jitterbit.

For an alternative approach to detecting changed records without sync flag fields, see Detect and deduplicate records using hash functions.