node.jsnetsuitesuitescriptsuitescript2.0netsuite-rest-api

403 error while calling NetSuite Restlet from Node.js


I am trying to create a Customer record in NetSuite by calling a deployed NetSuite Restlet script from Node.js. I am getting a 403 error in response. I am able to call the script successfully through Postman. I am not sure what's the issue when called from Node.js, because the authorization part in the code looks correct. Here's the code in Node.js:

function upsertUsingRestlet(body, callbackFn) {

  const baseUrl = `https://${NETSUITE_ACCOUNT_ID}.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=${SCRIPT_ID}&deploy=${DEPLOYMENT_ID}`;
  const oauthSignatureMethod = 'HMAC-SHA256';
  const oauthVersion = '1.0';
  const oauthNonce = crypto.randomBytes(32).toString('hex');
  const oauthTimestamp = Math.floor(Date.now() / 1000);

  const oauthParameters = {
    oauth_consumer_key: CONSUMER_KEY,
    oauth_token: ACCESS_TOKEN,
    oauth_nonce: oauthNonce,
    oauth_timestamp: oauthTimestamp,
    oauth_signature_method: oauthSignatureMethod,
    oauth_version: '1.0'
  };

  const sortedParameters = Object.keys(oauthParameters)
    .sort()
    .map((key) => `${key}=${oauthParameters[key]}`)
    .join('&');

  const signatureBaseString = `POST&${encodeURIComponent(baseUrl)}&${encodeURIComponent(sortedParameters)}`;
  const signingKey = `${CONSUMER_SECRET}&${ACCESS_TOKEN_SECRET}`;
  const hmac = crypto.createHmac('sha256', signingKey);
  hmac.update(signatureBaseString);
  let oauthSignature = hmac.digest('base64');
  oauthSignature = encodeURIComponent(oauthSignature);

  const headers = {
    'Prefer': 'transient',
    'Content-Type': 'application/json',
    'Authorization': `OAuth realm="${REALM}",oauth_nonce="${oauthNonce}",oauth_signature_method="${oauthSignatureMethod}",oauth_consumer_key="${CONSUMER_KEY}",oauth_token="${ACCESS_TOKEN}",oauth_timestamp="${oauthTimestamp}",oauth_version="${oauthVersion}",oauth_signature="${oauthSignature}"`
  };

  fetch(baseUrl, {
    'method': 'POST',
    'headers': headers,
    'body': body,
    'redirect': 'follow'
  })
    .then((response) => response)
    .then((data) => {
      callbackFn(data.status);
    })
    .catch((error) => {
      console.error('Error upsertOperation:', error);
    });
}

In Netsuite, View login and Audit trail - I see the error in Detail column as InvalidSignature.

However, the same authorization is working successfully when I call a netsuite REST API using the above code. The only difference was that it was a PUT request instead of POST. So, I am not sure why it is not working with Restlet.

Restlet script deployment screenshot for reference:

Enter image description here


Solution

  • It worked with the following changes:

    1. Added script and deploy in oauthParameters
    2. baseUrl while creating the signature should be without the parameters.

    Rest everything remains the same.

    Here's the updated code for reference:-

    function upsertUsingRestlet(body, callbackFn) {
    
      const baseUrl = `https://${NETSUITE_ACCOUNT_ID}.restlets.api.netsuite.com/app/site/hosting/restlet.nl`;
    const baseUrlWithParameters = baseUrl + `?script=${SCRIPT_ID}&deploy=${DEPLOYMENT_ID}`;
      const oauthSignatureMethod = 'HMAC-SHA256';
      const oauthVersion = '1.0';
      const oauthNonce = crypto.randomBytes(32).toString('hex');
      const oauthTimestamp = Math.floor(Date.now() / 1000);
    
      const oauthParameters = {
        script: SCRIPT_ID,
        oauth_consumer_key: CONSUMER_KEY,
        oauth_token: ACCESS_TOKEN,
        oauth_nonce: oauthNonce,
        oauth_timestamp: oauthTimestamp,
        oauth_signature_method: oauthSignatureMethod,
        oauth_version: '1.0',
        deploy: SCRIPT_DEPLOYMENT_ID
      };
    
      const sortedParameters = Object.keys(oauthParameters)
        .sort()
        .map((key) => `${key}=${oauthParameters[key]}`)
        .join('&');
    
      const signatureBaseString = `POST&${encodeURIComponent(baseUrl)}&${encodeURIComponent(sortedParameters)}`;
      const signingKey = `${CONSUMER_SECRET}&${ACCESS_TOKEN_SECRET}`;
      const hmac = crypto.createHmac('sha256', signingKey);
      hmac.update(signatureBaseString);
      let oauthSignature = hmac.digest('base64');
      oauthSignature = encodeURIComponent(oauthSignature);
    
      const headers = {
        'Prefer': 'transient',
        'Content-Type': 'application/json',
        'Authorization': `OAuth realm="${REALM}",oauth_nonce="${oauthNonce}",oauth_signature_method="${oauthSignatureMethod}",oauth_consumer_key="${CONSUMER_KEY}",oauth_token="${ACCESS_TOKEN}",oauth_timestamp="${oauthTimestamp}",oauth_version="${oauthVersion}",oauth_signature="${oauthSignature}"`
      };
    
      fetch(baseUrlWithParameters, {
        'method': 'POST',
        'headers': headers,
        'body': body,
        'redirect': 'follow'
      })
        .then((response) => response)
        .then((data) => {
          callbackFn(data.status);
        })
        .catch((error) => {
          console.error('Error upsertOperation:', error);
        });
    }