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:
Two custom boolean fields carry sync state, one in each external system:
- Salesforce:
Jitterbit_Synced__c:falsewhen a user last modified the record;truewhen 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:
- A user modifies an account in Salesforce. The automation rule sets
Jitterbit_Synced__c = false. - The Salesforce-to-NetSuite chain queries for records where
Jitterbit_Synced__c = falseand finds the modified record. - Studio writes the record to NetSuite with
custbody_jb_synced = true, then setsJitterbit_Synced__c = trueback in Salesforce. - 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
-
On the
Accountobject (or whichever object you are syncing), create a custom checkbox field with label Jitterbit Synced and API nameJitterbit_Synced__c. Set the default value to Unchecked. -
Create a workflow rule or Process Builder flow that fires on record creation or update by a user and sets
Jitterbit_Synced__ctofalse. This ensures that user-initiated changes are visible to the Salesforce-to-NetSuite chain.
In NetSuite
-
On the
Customerrecord type (or whichever record type you are syncing), create a custom checkbox field with label Jitterbit Synced and IDcustbody_jb_synced. Set the default value to unchecked. -
Create a user event SuiteScript (
afterSubmit) that fires on record save and setscustbody_jb_synced = false. Usecontext.typeto 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 example2020-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
-
On the design canvas, drag a Salesforce Query activity from an existing Salesforce endpoint onto the canvas to start the chain.
-
Double-click the activity to open its configuration.
-
Name: Enter
Query Salesforce Accounts. -
Object: Select Account.
-
Query conditions: Add two conditions:
Jitterbit_Synced__cequalsfalseLastModifiedDategreater than$sf_last_sync_time
Together these conditions return only records modified by users since the last sync run.
-
Select the fields to return:
Id,Name, and any other fields that map to NetSuite. -
Click Finished.
Step 2: Configure the transformation
-
Add a Transformation step after the Salesforce Query activity.
-
Set the source schema to the Salesforce Query response and the target schema to the NetSuite
Customerrecord structure. -
Map the Salesforce source fields to the corresponding NetSuite target fields.
-
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 = trueon every NetSuite record written by this chain is what prevents the NetSuite-to-Salesforce chain from picking them up and writing them back. -
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
-
Add a NetSuite Upsert activity after the transformation.
-
Name: Enter
Upsert NetSuite Customers. -
Record type: Select Customer.
-
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. -
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.
-
Add a Script step after the NetSuite Upsert activity.
-
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 = ""; -
Create a separate operation named
Mark Salesforce Records Syncedcontaining a Salesforce Update activity. Configure the activity to setJitterbit_Synced__c = trueon each record ID insynced_sf_ids. Use a transformation that splitssynced_sf_idson commas and maps each ID to an update payload. -
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
-
Drag a NetSuite Search activity onto the canvas.
-
Name: Enter
Search NetSuite Customers. -
Search filter: Add two conditions:
custbody_jb_syncedisfalselastmodifieddateafter$ns_last_sync_time
-
Return fields: Select
internalId,entityId,custentity_sf_id, and any other fields that map to Salesforce. -
Click Finished.
Step 2: Configure the transformation
-
Add a Transformation step after the NetSuite Search activity.
-
Set the source schema to the NetSuite Search response and the target schema to the Salesforce
Accountstructure. -
Map the NetSuite source fields to the corresponding Salesforce target fields.
-
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
-
Add a Salesforce Upsert activity after the transformation.
-
Name: Enter
Upsert Salesforce Accounts. -
External ID field: Select the field that stores the NetSuite internal ID in Salesforce (for example,
NetSuite_ID__c). -
Click Finished.
Step 4: Write the post-sync script
-
Add a Script step after the Salesforce Upsert activity.
-
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 = ""; -
Create a separate operation named
Mark NetSuite Records Syncedcontaining a NetSuite Update activity that setscustbody_jb_synced = trueon each record ID insynced_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.
-
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.
-
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
-
Deploy and run the Salesforce-to-NetSuite chain manually.
-
In the operation logs, confirm that records were retrieved from Salesforce and written to NetSuite.
-
In NetSuite, open one of the synced customer records and confirm that
custbody_jb_syncedis checked. -
In Salesforce, open the corresponding account and confirm that
Jitterbit_Synced__cis checked. -
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 = trueare excluded. -
In NetSuite, modify one of the synced customer records directly. The
afterSubmitSuiteScript resetscustbody_jb_synced = false. Run the NetSuite-to-Salesforce chain and confirm that the change propagates to Salesforce. -
In Salesforce, confirm the updated account has
Jitterbit_Synced__c = trueafter 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.