Looking for NetSuite ACS Alternatives? Try our Stratus Managed Services FREE for 1 Month. Speak To An Expert Now

Salesforce NetSuite Integration: Options, Setup and RESTlet Code

salesforce and netsuite integration code using restlets

By Louis Balla, Chief Revenue Officer, Nuage. Updated October 2026.

Salesforce and NetSuite connect in three ways: Oracle’s NetSuite Connector for Salesforce, an integration platform such as Boomi or Celigo, or custom code that pairs a NetSuite RESTlet with a Salesforce Apex callout. The usual first flow turns a Closed Won opportunity into a NetSuite sales order and sends order and fulfillment status back to Salesforce.

This guide starts with the options and what each one syncs. It then covers the authentication and API changes that affect any new build, and ends with a RESTlet and Apex sample for teams that write their own integration. The code is illustrative and belongs in sandboxes until your team has tested it against your own records.

Does NetSuite integrate with Salesforce?

Yes. Oracle publishes the NetSuite Connector for Salesforce, which an Administrator installs from the SuiteApps Marketplace after the NetSuite Connector Platform SuiteApp. Oracle’s order sync documentation describes three flows:

  • A Closed Won opportunity in Salesforce creates a sales order in NetSuite.
  • The NetSuite sales order syncs back to Salesforce as an order.
  • When the sales order is approved and fulfilled, the item fulfillment syncs back to Salesforce as a custom fulfillment object.

Oracle also documents financial syncs, so the sales team can see the status of sales orders in Salesforce. The order syncs apply only to sales orders created from a Salesforce Closed Won opportunity. A sales order entered directly in NetSuite does not take part.

Oracle’s setup page lists what to confirm before you start:

  • A NetSuite OneWorld account with at least one subsidiary, on either Legacy Tax or SuiteTax.
  • A Salesforce edition with API access: Enterprise, Developer or Professional Edition.
  • An API-only integration user in Salesforce, plus an External Client App for every edition except Professional.
  • Required features and preferences enabled in both systems before the SuiteApps are installed.

How do you integrate NetSuite with Salesforce?

Choose the method from the list of flows you need. Write down which records move, in which direction, and how quickly someone has to see the change. Then compare the three methods.

NetSuite Connector for Salesforce Integration platform (Boomi, Celigo, MuleSoft, Workato and others) Custom code (RESTlet and Apex)
What you set up Syncs and lookups configured in NetSuite and Salesforce Flows built or installed on the platform Scripts written and deployed in both systems
Fits when Your flows match the ones Oracle documents You connect more than two systems, or need flows the connector does not cover A custom object or business rule has no equivalent in a packaged product
Who maintains it Administrators in both systems The platform owner, usually with a partner A developer on each side, plus documentation
Check first OneWorld requirement and Salesforce edition Whether the NetSuite connection uses REST and OAuth 2.0 (see the timeline below) Credential storage, Salesforce governor limits and an error log

If your list matches what Oracle’s connector documents, start there. If it spans ecommerce, banking or several other systems, an integration platform can carry the whole set, and our Boomi and NetSuite guide shows what to plan for on that route. Expect some code when the flow involves a custom object or an approval rule that exists only in your company. Our guide to choosing a NetSuite integration provider covers how to compare firms for any of the three.

What data moves between Salesforce and NetSuite?

Most projects start with the same records. This table shows the common mapping. Oracle’s connector documents every flow in this table. A custom build can add others, such as products or custom objects, and each one is a design choice.

Salesforce NetSuite Direction Note
Account Customer (company) Salesforce to NetSuite Store the Salesforce Account ID on the customer so every later update finds the same record
Contact Contact Salesforce to NetSuite Oracle’s connector syncs the contacts on the connected opportunity
Closed Won opportunity Sales order Salesforce to NetSuite The first flow most teams build
Order Sales order NetSuite to Salesforce The approved NetSuite order returns to Salesforce
Fulfillment (custom object) Item fulfillment NetSuite to Salesforce Gives sales the shipping status without a call to finance
Financial status Invoice and payment NetSuite to Salesforce Oracle documents financial syncs for sales order status

Teams that ask how to connect Salesforce and NetSuite for unified forecasting need these same records plus a reporting layer, with pipeline from Salesforce next to booked and billed amounts from NetSuite. Our guide to combining NetSuite and Salesforce data for analytics covers that side.

Salesforce NetSuite integration using the REST API

Oracle offers three integration interfaces for NetSuite:

  • REST web services (SuiteTalk REST): record services plus a SuiteQL query service, authenticated with OAuth 2.0.
  • RESTlets: custom endpoints that you write in SuiteScript. You define the request and the JSON that comes back.
  • SOAP web services: the older SuiteTalk interface, which Oracle is retiring.

Salesforce supplies the other half. Apex callouts send requests out, and a Named Credential holds the endpoint and the authentication settings so the code never contains a secret. The Salesforce REST API handles calls in the other direction.

Oracle’s SOAP removal FAQ sets the timeline that matters for a new build:

  • 2025.2 is the last planned SOAP endpoint.
  • From 2026.1, newly built integrations should use REST web services with OAuth 2.0.
  • From 2027.1, no new integrations can use token-based authentication (TBA), which SOAP requires.
  • With the 2028.2 release, SOAP is no longer available and existing SOAP integrations stop working.

Oracle’s token-based authentication page adds that from 2027.1 no new integrations using TBA can be created for SOAP web services, REST web services or RESTlets, and that existing integrations continue to work. For RESTlets, Oracle’s authentication page recommends OAuth 2.0 and says NetSuite does not support user credentials authentication for new RESTlets.

When clients forward a retirement notice, our team inventories the integrations it affects. For a life sciences company we listed every active integration, confirmed each one came from a third-party application, and sent the client the vendor list and the published timeline so each vendor could confirm its plan. For an automotive software company, Salesforce’s retirement of its legacy SOAP login meant updating the Boomi connection, which our team tested in sandbox before the production change. An assessment at an equipment manufacturer counted 13 active token-based integrations to plan around ahead of the 2027.1 release.

Build it with RESTlets, part 1: the NetSuite side

This section builds a RESTlet that receives a Salesforce Account and creates or updates the matching NetSuite customer. Before you start:

  • Work in a NetSuite sandbox and a Salesforce sandbox, with an Administrator in each.
  • Enable the Server SuiteScript and OAuth 2.0 features in NetSuite, create an integration record, and give the integration role the permissions Oracle lists in its OAuth 2.0 for RESTlets documentation. That page names the scope as RESTLETS and describes the authorization code grant flow.
  • Create a free-form text field on the customer record with the ID custentity_sf_account_id. The script uses it to find the right customer on every later update.

Save this as sf_customer_restlet.js. The JSDoc block at the top is required, because SuiteScript reads the script type and API version from it:

/**
 * @NApiVersion 2.1
 * @NScriptType Restlet
 * @NModuleScope SameAccount
 */
define(['N/record', 'N/search', 'N/log'], (record, search, log) => {

  const post = (body) => {
    if (!body || !body.salesforceId || !body.name) {
      return { status: 'ERROR', message: 'salesforceId and name are required' };
    }

    // Look for a customer that already carries this Salesforce Account ID.
    const match = search.create({
      type: search.Type.CUSTOMER,
      filters: [['custentity_sf_account_id', 'is', body.salesforceId]],
      columns: ['internalid']
    }).run().getRange({ start: 0, end: 1 });

    try {
      if (match.length > 0) {
        record.submitFields({
          type: record.Type.CUSTOMER,
          id: match[0].id,
          values: { companyname: body.name, phone: body.phone || '' }
        });
        return { status: 'SUCCESS', customerId: match[0].id, action: 'updated' };
      }

      const customer = record.create({ type: record.Type.CUSTOMER });
      customer.setValue({ fieldId: 'companyname', value: body.name });
      customer.setValue({ fieldId: 'phone', value: body.phone || '' });
      customer.setValue({ fieldId: 'custentity_sf_account_id', value: body.salesforceId });
      // In a OneWorld account, also set the subsidiary here.
      const id = customer.save();
      return { status: 'SUCCESS', customerId: id, action: 'created' };
    } catch (e) {
      log.error('Customer sync failed', e.message);
      return { status: 'ERROR', message: e.message };
    }
  };

  return { post };
});

Deploy it:

  1. Upload the file to the SuiteScripts folder in the File Cabinet.
  2. Go to Customization, Scripting, Scripts, New. Select the file, choose the RESTlet type and save the script record.
  3. Create a deployment with the status Released. On the Audience tab, limit access to the integration role.
  4. Copy the external URL from the saved deployment. It has the form https://1234567.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=1234&deploy=1, with your own account ID and IDs. The Named Credential in part 2 points at the RESTlet domain shown on your deployment.

The script returns a JSON body with a status, so the Apex caller can tell a created, an updated and a rejected record apart even though the HTTP status is 200 in each case. Add a put or get function when you need other operations.

Build it with RESTlets, part 2: the Salesforce side

Create a Named Credential in Salesforce that points at your account’s RESTlet domain and carries the authentication settings. Salesforce’s Named Credentials support OAuth 2.0. How the credential obtains a NetSuite token depends on the flow you choose, so confirm it in the sandbox before you write any Apex.

The Apex class reads the credential by name, and the trigger hands changed accounts to a Queueable job. A callout cannot run directly inside a trigger, and the Queueable keeps the user’s save fast.

public with sharing class NetSuiteAccountSync implements Queueable, Database.AllowsCallouts {
    private List<Id> accountIds;

    public NetSuiteAccountSync(List<Id> accountIds) {
        this.accountIds = accountIds;
    }

    public void execute(QueueableContext ctx) {
        // Salesforce allows 100 callouts per transaction. Split larger lists into several jobs.
        for (Account a : [SELECT Id, Name, Phone FROM Account WHERE Id IN :accountIds]) {
            HttpRequest req = new HttpRequest();
            req.setEndpoint('callout:NetSuite_RESTlet/app/site/hosting/restlet.nl?script=1234&deploy=1');
            req.setMethod('POST');
            req.setHeader('Content-Type', 'application/json');
            req.setBody(JSON.serialize(new Map<String, Object>{
                'salesforceId' => a.Id,
                'name' => a.Name,
                'phone' => a.Phone
            }));

            HttpResponse res = new Http().send(req);
            Boolean ok = false;
            if (res.getStatusCode() == 200) {
                Map<String, Object> result = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
                ok = 'SUCCESS'.equals((String) result.get('status'));
            }
            if (!ok) {
                System.debug(LoggingLevel.ERROR, 'NetSuite sync failed for ' + a.Id + ': ' + res.getBody());
                // Write the payload and response to a log object here.
            }
        }
    }
}
trigger AccountNetSuiteSync on Account (after insert, after update) {
    List<Id> changed = new List<Id>();
    for (Account a : Trigger.new) {
        if (Trigger.isInsert
            || a.Name != Trigger.oldMap.get(a.Id).Name
            || a.Phone != Trigger.oldMap.get(a.Id).Phone) {
            changed.add(a.Id);
        }
    }
    if (!changed.isEmpty()) {
        System.enqueueJob(new NetSuiteAccountSync(changed));
    }
}

To send a Closed Won opportunity, add a second trigger on Opportunity that enqueues a similar job against a RESTlet that creates the sales order. Use the NetSuite internal IDs of the customer and the items in the payload, because names change.

Sending NetSuite status back to Salesforce

The return direction runs from a NetSuite user event script that calls the Salesforce REST API after a record is saved.

A healthcare products company wanted two sales order status fields to flow back to Salesforce in real time, and the script built for it only sent an update when a user opened and saved the order in NetSuite. Our developer replaced that client-side script with a server-side user event script that runs after any save, whether the change comes from the screen, a workflow, an integration or a CSV import, kept the same field mapping and RESTlet, and packaged the whole change so it moved to production in one step. The client’s Salesforce developer then switched the callout to OAuth 2.0, and we deployed both scripts.

Custom objects, limits and keys

  • Custom objects: A RESTlet works with any record type you script. To sync a Salesforce custom object, define the JSON for it and have the RESTlet create or update the NetSuite custom record that corresponds to it. Whether Oracle’s connector covers a particular custom object is a question for its setup documentation; an integration platform handles it through field mapping.

  • Concurrency: Oracle’s concurrency governance page says the limit for concurrent requests depends on the service tier and the number of SuiteCloud Plus licenses, and applies to web services and RESTlet requests together. Several integrations in one account share that limit, so queue requests and retry with a delay.

  • Keys: Keep one identifier per direction: the Salesforce Account ID on the NetSuite customer, and the NetSuite internal ID on the Salesforce account. Look records up by those, never by name.

  • Errors: Log the payload, the response and the record that failed, in a custom object in Salesforce or a custom record in NetSuite.

Integration and interface work made up 363 of 3,241 client requests our team classified (11.2%), and in 226 of them the root cause sat at the boundary with the connected system: a mapping gap, an expired credential or a sync that stopped. These requests close more slowly than most. Of the 111 that had closed when we measured, the median took 29.2 calendar days, and across all requests an integration root cause took a median of 39 days against 23 for a configuration change, usually because the fix depends on a second system’s owner or on credentials the client has to supply.

How Nuage approaches Salesforce and NetSuite integration

In recent integration projects and assessments, our team has taken these steps. The order changes with each account.

  1. List the integrations that already run. We inventory each one, its owner and its authentication method, so a retirement notice or an expired credential does not arrive as a surprise.
  2. Write down the flows and the owner of each field. Customers, orders, fulfillment and billing status each get a source system, a direction and an ID that links the two records.
  3. Pick the method for each flow. Oracle’s connector, an integration platform or a script, based on the list in the previous step.
  4. Run user event scripts on the server. A script that runs after any save catches changes from workflows, imports and other integrations as well as from the screen.
  5. Test in sandbox, then move the whole change at once. Scripts, the RESTlet and the field mapping are packaged together so production receives one change.
  6. Watch the boundary with the connected system. In 226 of the 363 integration requests we classified, the root cause sat there, such as a mapping gap, an expired credential or a sync that stopped.

Nuage’s NetSuite experts do this work as part of ongoing NetSuite optimization and managed services. For the wider picture of NetSuite integration methods, see our NetSuite integration overview.

Salesforce and NetSuite integration FAQ

Is a unified API faster than coding the integration ourselves?

A packaged connector or integration platform moves authentication, retries and field mapping into a product, so setup is configuration rather than code. Whether that is faster for a fixed launch date depends on how closely your flows match what the product supports. Test your two or three required flows in a sandbox before you commit to a date.

Can I sync Salesforce custom objects to NetSuite?

Yes, with a RESTlet or an integration platform. A RESTlet needs a JSON payload for the object and a script that creates or updates the NetSuite record. A platform maps the fields in its own interface. Check Oracle’s setup documentation before you assume the connector covers a specific custom object.

Which authentication should a new RESTlet integration use?

OAuth 2.0. Oracle recommends it for RESTlets, says NetSuite does not support user credentials authentication for new RESTlets, and says that from 2027.1 no new integrations can be created with token-based authentication. Existing token-based integrations continue to work, so plan their move to OAuth 2.0 when you next change them.

Is the sync real time?

That depends on how each direction is built. An Apex trigger with a Queueable job sends changes within moments, while a scheduled job or a nightly platform run does not. Decide the speed for each direction separately, and tell the teams who read the data how fresh it is.

Do I need a OneWorld account?

Oracle’s NetSuite Connector for Salesforce requires a OneWorld account with at least one subsidiary, and a Salesforce edition with API access. A RESTlet integration does not carry the OneWorld requirement, though in a OneWorld account every customer the script creates needs a subsidiary.

About the author

Louis Balla is Chief Revenue Officer at Nuage, a NetSuite optimization partner that has served 250+ clients since 2011 and holds a 4.9 out of 5 rating on Clutch. The client integration work in this article is our team’s.

What to read next

Your 90-Day Plan to Put AI Agents to Work in NetSuite (Safely)

90-day

Building a Finance Team That Can Trust Its AI

finance team

AI and Your Inventory: 10 Questions Before You Trust the Numbers

inventory