The Setup Starts in NetSuite, Not Salesforce
Every NetSuite to Salesforce integration starts in the same place, and it is not Salesforce. Before a single sales order syncs or a customer record flows, NetSuite needs to know exactly which application is calling its APIs, which user it is acting as, and what that user's role is allowed to touch. Token-Based Authentication (TBA) answers all three with two key pairs: a Consumer Key and Secret that identify the application, and a Token ID and Secret that bind the application to a specific user and role. Every request then carries an Authorization: OAuth header signed per-request with HMAC-SHA256, so no password ever crosses the wire.
This is the pattern Cloudsheer has shipped across three client projects, and this article is the zoomed-in first step of our full technical guide at cloudsheer.com/integrations/netsuite/technical-guide. One honest note up front: NetSuite is closing TBA to newly created integrations starting with release 2027.1 in favor of OAuth 2.0, but TBA remains the dominant pattern in production today and every major middleware platform speaks it natively. We flag the OAuth 2.0 path where it matters below.
What You Need Before You Start
- A NetSuite Administrator login, or a role with the Integration Application and Access Token Management permissions. Standard users cannot create integration records or tokens.
- A Salesforce org with API access and rights to create Named Credentials and deploy Apex, or admin access to your middleware platform (Celigo, Boomi, MuleSoft) if the signing will live there.
- A password manager or secrets vault open and ready. NetSuite shows two of the four credentials exactly once, with no way to view them again.
- Postman, or curl plus an OAuth 1.0 signing library, for the smoke test before you write any Salesforce code.
- A transport decision: SuiteTalk REST is the right default. SOAP still supports TBA but is slated for removal by 2028.2, so do not start a new build on it.
- About 45 minutes in the NetSuite UI, uninterrupted. The shown-once credential pages punish multitasking.
Step 1: Enable the Features NetSuite Hides Behind SuiteCloud
In NetSuite, go to Setup > Company > Enable Features and click the SuiteCloud subtab. Three sections matter here.
- In the SuiteTalk (Web Services) section, check REST WEB SERVICES. Check SOAP WEB SERVICES only if a legacy dependency forces you onto SOAP.
- In the Manage Authentication section, check TOKEN-BASED AUTHENTICATION. If you expect to adopt the OAuth 2.0 machine-to-machine flow later, check OAUTH 2.0 while you are here.
- If the integration will call RESTlets (custom SuiteScript endpoints), also check CLIENT SUITESCRIPT and SERVER SUITESCRIPT in the SuiteScript section.
- Click Save. NetSuite typically pops a SuiteCloud Terms of Service dialog for each newly enabled feature; accept each one to complete the save.
Step 2: Create the Integration Record (First Shown-Once Wall)
Go to Setup > Integration > Manage Integrations > New. Only Administrators or users with the Integration Application permission can see this page.
- Enter a Name that will still make sense in an audit log next year, like Salesforce Order Sync - Prod. Leave State set to Enabled.
- On the Authentication subtab, check TOKEN-BASED AUTHENTICATION. Leave TBA: AUTHORIZATION FLOW unchecked - that browser-based flow needs a callback URL, and this machine-to-machine setup does not. Leave every OAuth 2.0 grant box unchecked too.
- Click Save. The confirmation page shows CONSUMER KEY / CLIENT ID and CONSUMER SECRET / CLIENT SECRET exactly once. Copy both into your password manager immediately, before you click anything else, and treat them like production passwords from this second onward: no email, no Slack, no spreadsheet.
- If you navigate away without copying, the pair is gone forever. The only recovery is Edit > Reset Credentials, which invalidates the old pair in every system that uses it.
Step 3: Build a Least-Privilege Integration Role and Assign It
Never run an integration as Administrator. Create a role that can do exactly what the sync needs and nothing else. Go to Setup > Users/Roles > User Management > Manage Roles > New.
- On the Permissions subtab, open the Setup subtab and add Log in using Access Tokens. This lets the role authenticate with tokens but not mint them, which is exactly what an integration identity should be able to do.
- Still under Setup, add REST Web Services. Add SOAP Web Services or SuiteScript only if you are actually calling those surfaces.
- On the Transactions, Lists, and Reports subtabs, add only the record permissions the sync needs, for example Customers and Sales Order at Full for a bidirectional order sync. Every permission you skip is one less thing a leaked token can touch.
- Save the role, then assign it to a dedicated integration user: Lists > Employees > Employees > Edit the user > Access tab > add the role on the Roles sublist > Save.
Step 4: Mint the Access Token (Second Shown-Once Wall)
Log in as an Administrator or a user with Access Token Management, then go to Setup > Users/Roles > Access Tokens > New. Some accounts label the path Setup > Users/Roles > User Management > Access Tokens; both land on the same page.
- Select Application Name (the Step 2 integration record), User (your integration user), and Role (the Step 3 role). Token Name auto-populates; leave it.
- Click Save. The confirmation page shows TOKEN ID and TOKEN SECRET exactly once. Copy both now. Unlike the consumer pair, there is no reset at all: lose these and you create a brand-new token and rewire every consumer.
- Know the admin quirk: a user with Access Token Management cannot create tokens for Administrator-role users, and Administrators can only create Administrator tokens for themselves. Your least-privilege role sidesteps the whole problem.
- TBA tokens never expire. They stay valid until revoked or the user or role is inactivated, and password rotation policies do not touch them, so put a manual rotation date on the calendar today.
Step 5: Find Your Account ID and Get the Realm Right
Go to Setup > Company > Company Information and copy the ACCOUNT ID field. This one value gets formatted two different ways, and mixing them up is the most common cause of signature failures we see in delivery.
- The REST base URL uses the lowercase hyphenated form: https://1234567-sb1.suitetalk.api.netsuite.com for a sandbox, https://1234567.suitetalk.api.netsuite.com for production.
- The realm parameter in the OAuth header uses the uppercase underscore form: realm="1234567_SB1" for that same sandbox, realm="1234567" for production. Sending 1234567-sb1 as the realm fails the signature check.
- Sandbox warning: tokens are environment-specific and sandbox refreshes do not carry them over. After every refresh you recreate the token in the refreshed account. Put that in your release runbook now, not after the first mystery outage.
Step 6: Wire the Credentials Into Salesforce
Here is the part most tutorials skip: Salesforce Named and External Credentials have no OAuth 1.0a HMAC signing support, so classic TBA cannot be configured declaratively. You also do not need an Auth Provider - Auth Providers exist for browser-based OAuth flows, and TBA with manually issued tokens has no browser step and no callback URL. Across our three client builds, the wiring has taken one of three shapes.
- Middleware, the most common: Celigo integrator.io, Boomi, and MuleSoft all implement NetSuite TBA natively. Paste the four secrets into the connector's NetSuite connection screen and the platform handles signing, nonces, and timestamps. Salesforce talks to the middleware and never signs anything itself.
- Apex signing helper: create a Named Credential pointing at https://<accountid>.suitetalk.api.netsuite.com for endpoint management, keep the four secrets in an External Credential principal or protected Custom Metadata, and build the header in code. The signature is Crypto.generateMac('hmacSHA256', Blob.valueOf(baseString), Blob.valueOf(encodedConsumerSecret + '&' + encodedTokenSecret)), with a fresh random nonce and current timestamp on every single request. Open-source references like the ApexNetsuite repo on GitHub show the full base-string construction.
- OAuth 2.0 client credentials (M2M), the forward path: check CLIENT CREDENTIALS (MACHINE TO MACHINE) GRANT on the integration record, upload a public certificate at Setup > Integration > Manage Authentication > OAuth 2.0 Client Credentials (M2M) Setup, and POST a signed JWT assertion to https://<accountid>.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token for a Bearer token valid exactly 3600 seconds. New builds should target this before the 2027.1 cutoff for new TBA integrations.
Step 7: Smoke Test With Postman Before You Write Apex
Prove the credentials work from Postman first, so any failure is a NetSuite configuration problem and not your code.
- Create a GET request to https://<accountid>.suitetalk.api.netsuite.com/services/rest/record/v1/customer?limit=1.
- On the Authorization tab choose OAuth 1.0. Set Signature Method to HMAC-SHA256 (HMAC-SHA1 has been rejected since NetSuite 2023.1), fill in Consumer Key, Consumer Secret, Access Token (the Token ID), and Token Secret, set Realm to the uppercase underscore account ID, and enable Add auth data to request headers.
- Send. A 200 with a JSON items array means all four credentials, the role, and the realm are correct. A 401 INVALID_LOGIN_ATTEMPT means one of them is not - go straight to the Login Audit Trail below instead of guessing.
- Do not test by replaying a captured request. Every TBA signature embeds a single-use nonce and timestamp pair, so a replay fails with nonce_used by design.
Common Errors and How to Fix Them
The API only ever returns a generic 401 Invalid login attempt. The real reason lives at Setup > Users/Roles > User Management > View Login Audit Trail: check Use Advanced Search, open the Results subtab, and add the Detail, Token-based Access Token Name, and Token-based Application Name columns. Then match the Detail value here.
- InvalidSignature: the base string or signing key is wrong. Verify the HTTP method is uppercase, every query parameter is in the sorted parameter set, the key is percentEncode(consumerSecret) + '&' + percentEncode(tokenSecret), and the realm is 1234567_SB1, not 1234567-sb1. One character out of order breaks the whole signature.
- InvalidTimestamp: oauth_timestamp is more than 5 minutes off NetSuite server time. Sync the caller with NTP and generate the header immediately before sending, never inside a retry queue that reuses old headers.
- UnknownAlgorithm: you signed with HMAC-SHA1, rejected since 2023.1. Switch to HMAC-SHA256.
- token_rejected: the Token ID does not exist in this environment or is bound to a different integration record. Recreate the token against the right application, and remember that sandbox refreshes wipe tokens.
- consumer_key_refused: the integration record State is Blocked. Setup > Integration > Manage Integrations > set State back to Enabled.
- permission_denied: the user or role is inactive, or the role lost Log in using Access Tokens or REST Web Services. Re-check the Step 3 wiring.
- nonce_used: a nonce and timestamp pair was replayed, usually a retry bug or two threads sharing one generator. Use a cryptographically random nonce of about 20 characters per request.
- FeatureDisabled: Token-Based Authentication or the web services feature got unchecked at Setup > Company > Enable Features. Re-run Step 1.
Security Checklist Before You Ship
- All four secrets live in a vault, an External Credential principal, or protected Custom Metadata - never in email, Slack, spreadsheets, or debug logs.
- The integration runs under a dedicated user with a least-privilege role, not an Administrator and not a human's login.
- The role has Log in using Access Tokens rather than User Access Tokens, so the integration identity cannot mint new tokens for itself.
- A rotation date is on the calendar. TBA tokens never expire on their own, so nobody rotates them unless you decide to.
- Stale tokens are revoked at Setup > Users/Roles > Access Tokens; revoked tokens stay visible for audit, which is exactly what you want.
- The Login Audit Trail search from the troubleshooting section is saved and reviewed, so failed token logins get noticed instead of discovered.
- The sandbox refresh runbook includes recreating tokens and re-entering credentials in middleware or Apex config.
Where This Fits in the Full NetSuite Build
Authentication is step one of roughly ten. Once the smoke test passes you still have to map NetSuite records to Salesforce objects, pick sync direction and conflict rules, respect NetSuite's concurrency limits, and build error handling that survives a sandbox refresh. The full walkthrough lives at cloudsheer.com/integrations/netsuite/technical-guide, and this article is the zoomed-in version of its first step.
If you build with Claude Code, we packaged the whole playbook as a free skill: run npx skills add shivamgoel-cloudsheer/Claude-skills --skill netsuite-salesforce and your agent gets the click paths, the signing logic, and the error table from this article as working context.
How Cloudsheer Can Help
Cloudsheer has shipped this exact integration across three client projects, covering middleware-managed syncs and pure Apex signing implementations, with OAuth 2.0 M2M as the path we now recommend for new builds. We know where the shown-once walls are, which Detail codes actually mean what, and how to structure the role so a security review passes the first time.
If you would rather hand this off, we scope NetSuite plus Salesforce integrations end to end: auth setup, field mapping, middleware selection, and post-go-live monitoring. Book a free discovery call at cal.com/cloudsheer-consulting/30min.
