Query live customer data from your NetSuite ERP in a WhatsApp chatbot. This guide covers NetSuite's authentication options, the SuiteQL query approach, and the recommended middleware pattern for the simplest production-ready integration.
Before following this guide, read the External API Request step foundation guide. It covers every field in the step interface so this guide can focus on NetSuite-specific values.
NetSuite is an enterprise ERP, not a developer-first SaaS. Every integration path requires an Administrator to create an Integration Record in NetSuite. The most common authentication method (TBA/OAuth 1.0a) also requires a cryptographically signed request header that standard HTTP tools cannot generate without custom code. Read this guide fully before starting. The RESTlet middleware approach is often the simplest path for most teams.
NetSuite offers two practical authentication methods for external integrations. Here is an honest comparison:
| Method | Complexity | Who does it | Best for |
|---|---|---|---|
| OAuth 2.0 + SuiteQL | High: IT admin setup + dev involvement | NetSuite admin + developer | Teams already using OAuth 2.0 tools, greenfield integrations |
| RESTlet (middleware) | Medium: write once in SuiteScript | NetSuite developer (write once) | Quickest path to a working WhatsApp bot, recommended for most teams |
| TBA (OAuth 1.0a) | Very high: signed headers per-request | Developer only | Legacy integrations; avoid for new builds, being phased out |
This guide covers Path A (OAuth 2.0 + SuiteQL) and Path B (RESTlet middleware). TBA is noted for reference but not recommended for new builds.
Before either path, your NetSuite admin completes these steps:
1234567). It also appears in your URL: 1234567.app.netsuite.com.Your NetSuite instance URL follows the pattern https://ACCOUNT_ID.app.netsuite.com. The API base URL is https://ACCOUNT_ID.suitetalk.api.netsuite.com/services/rest. All sandbox accounts append _SB1 to the account ID but use a hyphen in the URL: ACCOUNT_ID-sb1.
OAuth 2.0 requires your IT team to complete an authorization code flow once to get a Bearer token and refresh token. After that, WA.Expert's External API Request step can call NetSuite directly.
1. In Integration Record (Setup > Integration > Manage Integrations):
Enable OAuth 2.0, set callback URL, note Client ID and Client Secret.
2. Direct the NetSuite admin to authorise at:
https://ACCOUNT_ID.app.netsuite.com/app/login/oauth2/authorize.nl
?client_id=YOUR_CLIENT_ID
&response_type=code
&redirect_uri=YOUR_CALLBACK_URL
&scope=rest_webservices
&state=RANDOM_STRING
3. After admin approves, exchange the code for tokens:
POST https://ACCOUNT_ID.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token
Content-Type: application/x-www-form-urlencoded
Body:
grant_type=authorization_code
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&redirect_uri=YOUR_CALLBACK_URL
&code=AUTHORISATION_CODE
4. Response:
{ "access_token": "...", "refresh_token": "...", "expires_in": 3600 }
Store the access_token and refresh_token.
Refresh using refresh_token before the 1-hour expiry.Once you have a Bearer token, add an External API Request step in WA.Expert. SuiteQL queries are sent as POST requests with a JSON body:
NetSuite's SuiteQL endpoint requires the Prefer: transient header on every POST request. Without it, the API returns a 400 error. This is unique to NetSuite. No other platform in this series requires it.
{
"totalResults": 1,
"count": 1,
"hasMore": false,
"items": [
{
"id": "142",
"companyname": "Sharma Textiles Pvt Ltd",
"email": "priya@sharma.com",
"phone": "9820000001"
}
],
"offset": 0
}
Map: items[0].companyname, items[0].email, items[0].phoneNetSuite SuiteQL returns field names in lowercase regardless of how they appear in the UI. companyName in the UI becomes companyname in the API response. Map as items[0].companyname (all lowercase).
A RESTlet is a custom script you deploy inside NetSuite that exposes a simple HTTP endpoint. Your NetSuite developer writes it once, typically 30 lines of SuiteScript. After deployment, WA.Expert calls it with a plain API key you define, and the RESTlet handles all the NetSuite querying internally.
What your NetSuite developer deploys:
A SuiteScript 2.x RESTlet at a URL like:
https://ACCOUNT_ID.restlets.api.netsuite.com/app/site/hosting/restlet.nl
?script=SCRIPT_ID&deploy=DEPLOY_ID&apikey=YOUR_SIMPLE_KEY&phone=PHONE_NUMBER
The RESTlet internally:
1. Validates the simple apikey
2. Queries res.customer WHERE phone = phone_param
3. Returns clean JSON: { "name": "...", "email": "...", "city": "..." }
What WA.Expert calls (simple, no OAuth required):
GET https://ACCOUNT_ID.restlets.api.netsuite.com/app/site/hosting/restlet.nl
?script=123&deploy=1&apikey=MY_SIMPLE_KEY&phone={{customer_phone}}
Authorization: NLAuth nlauth_account=ACCOUNT_ID,nlauth_email=USER@COMPANY.COM,
nlauth_signature=PASSWORD,nlauth_role=ROLE_ID
Note: NLAuth uses the NetSuite admin's email + password in the header.
This is simpler than OAuth 1.0a TBA but still requires setup.
Ask your NetSuite admin for the NLAuth header values.Your NetSuite developer writes the RESTlet once and controls what data it exposes. WA.Expert's External API Request step then calls a clean simple endpoint. No per-request OAuth 1.0a signature generation. No token expiry to manage. One API call per chatbot lookup.
| What you want | SuiteQL query |
|---|---|
| Customer by phone | SELECT id, companyName, email, phone FROM customer WHERE phone = '9820000001' LIMIT 1 |
| Customer by email | SELECT id, companyName, phone FROM customer WHERE email = 'priya@example.com' LIMIT 1 |
| Customer by company name | SELECT id, companyName, email FROM customer WHERE companyName LIKE '%Sharma%' LIMIT 10 |
| Latest sales order for customer | SELECT id, tranDate, status, total FROM transaction WHERE type = 'SalesOrd' AND entity = '142' ORDER BY tranDate DESC LIMIT 1 |
| Open invoices for customer | SELECT id, dueDate, amountRemaining FROM transaction WHERE type = 'CustInvc' AND entity = '142' AND status = 'open' |
In SuiteQL, 'customer' is the internal table name. 'entity' is the customer's internal ID. Field names return lowercase in the response. NetSuite's internal IDs are numeric strings.
| Symptom | Likely cause | Fix |
|---|---|---|
| 400 on SuiteQL endpoint | Missing Prefer: transient header | Add Prefer: transient as a header on the SuiteQL POST request. |
| 401 Unauthorized | Invalid or expired access token | Refresh the OAuth 2.0 token using your refresh_token. Tokens expire after 1 hour. |
| 403 Forbidden | Integration role lacks read permission | In NetSuite, go to the role assigned to the integration user and add Customer > View permission. |
| Empty items array | Customer not found or phone format mismatch | Try the query with just a partial phone number or check how NetSuite stores phone numbers (with/without country code, spaces, dashes). |
| Account ID format error | Sandbox IDs use hyphen, not underscore | Sandbox account ID in URL: 1234567-sb1 (hyphen). In some forms NetSuite uses 1234567_SB1 (underscore). Use hyphen in API URLs. |
| Integration Record not visible | Insufficient NetSuite permissions | Creating Integration Records requires the Administrator role in NetSuite. Ask your NetSuite admin. |
Microsoft Dynamics 365: Azure AD OAuth, OData filters. Another enterprise ERP guide.
Read guide →Odoo ERP: Bearer API key, res.partner model, domain filter syntax.
Read guide →Salesforce REST API with SOQL queries: Connected App setup.
Read guide →Free trial, no credit card required. And if you ever get stuck, we are the only platform in India that answers you live on WhatsApp.