Hi all, having some painful challenges with sendin...
# suitescript
l
Hi all, having some painful challenges with sending a POST call to my Restlet endpoint. It works fine when testing in Postman, but when I go to test with some python code it keeps showing Invalid Signature in Login Audit Trail in Netsuite. Any help would be lifesaving, Can't count how many different pages/forums/methods I've tried to replicate to ultimately meet the same fate. Code in thread.
1
Copy code
import time
import json
import math
import urllib
import random
import hashlib
import hmac
import base64
import requests
from flask import Flask, request, jsonify
from authlib.common.encoding import to_bytes

CONSUMER_KEY = "CONSUMER_KEY"
CONSUMER_SECRET = "CONSUMER_SECRET"
TOKEN_ID = "TOKEN_ID"
TOKEN_SECRET = "TOKEN_SECRET"
NETSUITE_ACCOUNT_ID = '1234567_SB1'
SIGN_METHOD = 'HMAC-SHA256'
OAUTH_VERSION = '1.0'

RESTLET_URL = "<https://1234567-sb1.restlets.api.netsuite.com/app/site/hosting/restlet.nl>?"

def main():
    webhook_payload = json.dumps({
        "from": "0123456789",
        "to": "0123456789",
        "message": "message_sent",
        "salesOrderId": "123456",
        "msgref": "123456789"
    })

    response = forward_to_netsuite(webhook_payload)
    print(response)

def getAuthNonce():
    nonce_text = ''
    length = 11
    possible= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
    for i in range(length):
        nonce_text += possible[math.floor(random.uniform(0, 1) * len(possible))]
    return nonce_text

def getSignature(BASE_URL, HTTP_METHOD, OAUTH_NONCE, TIME_STAMP):
    print(TIME_STAMP)
    data = 'deploy=' + '1' + "&"
    data += '&oauth_consumer_key' + "=" + CONSUMER_KEY + "&"
    data += 'oauth_nonce' + "=" + OAUTH_NONCE + "&"
    data += 'oauth_signature_method' + "=" + SIGN_METHOD + "&"
    data += 'oauth_timestamp' + "=" + str(TIME_STAMP) + "&"
    data += 'oauth_token' + "=" + TOKEN_ID + "&"
    data += 'oauth_version' + "=" + OAUTH_VERSION
    data += 'script=' + '1269'
    signatureValue = HTTP_METHOD + '&' + urllib.parse.quote(BASE_URL, safe='~()*!.\'') + '&' + urllib.parse.quote(data,safe='~()*!.\'')
    signatureKey = urllib.parse.quote(CONSUMER_SECRET, safe='~()*!.\'') + '&' + urllib.parse.quote(TOKEN_SECRET,safe='~()*!.\'')
    print("signatureValue: " + signatureValue)
    print("signatureKey: " + signatureKey)
    shaData = hmac.new(to_bytes(signatureKey), to_bytes(signatureValue), hashlib.sha256)
    base64EncodedData = base64.b64encode(shaData.digest())[:-1]
    oauth_signature = base64EncodedData.decode('utf-8')
    oauth_signature = urllib.parse.quote(oauth_signature, safe='~()*!.\'')
    print("oauthsignature quote: " + oauth_signature)
    return oauth_signature

def createHeader():
    BASE_URL = RESTLET_URL
    OAUTH_NONCE = getAuthNonce()
    TIME_STAMP = round(time.time())
    HTTP_METHOD = "POST"
    oauth_signature = getSignature(BASE_URL, HTTP_METHOD, OAUTH_NONCE, TIME_STAMP)
    OAuthHeader = 'OAuth '
    OAuthHeader += 'realm=\"' + NETSUITE_ACCOUNT_ID + '\",'
    OAuthHeader += 'oauth_consumer_key=\"' + CONSUMER_KEY + '\",'
    OAuthHeader += 'oauth_token=\"' + TOKEN_ID + '\",'
    OAuthHeader += 'oauth_signature_method="' + SIGN_METHOD + '",'
    OAuthHeader += 'oauth_timestamp=\"' + str(TIME_STAMP) + '\",'
    OAuthHeader += 'oauth_nonce=\"' + OAUTH_NONCE + '\",'
    OAuthHeader += 'oauth_version=\"1.0\",'
    OAuthHeader += 'oauth_signature=\"' + oauth_signature + '\"'
    print(oauth_signature)
    print(OAUTH_NONCE)
    print(OAuthHeader)

    headers = {
        "Content-Type": "application/json",
        "Authorization": OAuthHeader,
        "Host": "<http://1234567-sb1.restlets.api.netsuite.com|1234567-sb1.restlets.api.netsuite.com>",
        "Cache-Control": "no-cache",
        "Cookie": "NS_ROUTING_VERSION=LAGGING"

    }
    return headers

def forward_to_netsuite(data):
    headers = createHeader()
    response = requests.request("POST", RESTLET_URL, headers=headers, data=data)

    return response.json(), response.status_code

if __name__ == '__main__':
    main()
And the resulting authorization header comes out as follows:
OAuth realm="1234567_SB1",oauth_consumer_key="CONSUMER_KEY",oauth_token="TOKEN_KEY",oauth_signature_method="HMAC-SHA256",oauth_timestamp="1712208251",oauth_nonce="8wU7UP8pr7V",oauth_version="1.0",oauth_signature="w3Br9xx%2FBNA6A6wnccVBFBPEJT1II9aN0LbHTExdVY8"
b
first choice is always to use an existing package first
in this case, i recommend requests-oauthlib
l
Yeah, one of my earlier attempts was using that package, I'll give it another try. I also tried Netsuite-Connector if anyone's had experience with that? It didn't go as smoothly as expected
b
what did your attempt look like
NetSuite-Connector was built using requests-oauthlib, so its use is going to be similar
e
you have to build your signature using the canonical order of values
something like this...
var data = 'deploy=' + SCRIPT_DEPLOYMENT_ID + '&'; data += 'oauth_consumer_key=' + OAUTH_CONSUMER_KEY + '&'; data += 'oauth_nonce=' + OAUTH_NONCE + '&'; data += 'oauth_signature_method=' + OAUTH_SIGNATURE_METHOD + '&'; data += 'oauth_timestamp=' + OAUTH_TIMESTAMP + '&'; data += 'oauth_token=' + OAUTH_TOKEN + '&'; data += 'oauth_version=' + OAUTH_VERSION + '&'; data += 'status=' + 'complete'; data += 'script=' + SCRIPT_ID;
n
@Luke Collins I've sent you a DM with a simple Python script that uses:
import requests
from requests_oauthlib import OAuth1
Hopefully it gives you what you need (albeit my example is using SuiteTalk).
l
@NElliott Thanks for that, replied in DM.
@battk I've just tried again with requests-oauthlib, following your and Neil's advice. I also tried calling via Postman then immediately via my Python, then copied the timestamp from my Python into a new Postman call which worked fine. Which suggests the timestamp isn't the issue (another suggested fix I've seen a lot). The nonce I generate also seems completely fine. Not 100% sure what I'm missing. I also tried the different ordering of the data values but it didn't work in any order I've tried, thanks for the idea though! Code of the requests-oauthlib follows:
Copy code
import requests
import json
from requests_oauthlib import OAuth1


RESTLET_URL = "<https://1234567-sb1.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=1269&deploy=1>"
CLIENT_KEY = "C_KEY"
CLIENT_SECRET = "C_SECRET"
TOKEN_KEY = "T_KEY"
TOKEN_SECRET = "T_SECRET"

http_method = "POST"
realm = "1234567_SB1"

def main():
    oauth = OAuth1(CLIENT_KEY, CLIENT_SECRET, TOKEN_KEY, TOKEN_SECRET, 
                   signature_method="HMAC-SHA256", realm=realm)
    payload = {
        "from": "0123456789"
    }
    json_payload = json.dumps(payload)
    headers = {
        "prefer": "transient",
        "Content-Type": "application/json",
        "Cookie": "NS_ROUTING_VERSION=LAGGING"
    }
    response = requests.request("POST", url=RESTLET_URL, auth=oauth, headers=headers, data=json_payload)
    print(response.content, response)

if __name__ == '__main__':
    main()
b
your code works for me
you are likely messing up around
Copy code
RESTLET_URL = "<https://1234567-sb1.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=1269&deploy=1>"
CLIENT_KEY = "C_KEY"
CLIENT_SECRET = "C_SECRET"
TOKEN_KEY = "T_KEY"
TOKEN_SECRET = "T_SECRET"

http_method = "POST"
realm = "1234567_SB1"
although that probably shouldnt give a signature error, so you should take a closer look at the login audit trail details for the exact error
l
Okay solved, thank you everyone for your help! There was somehow a single incorrect character in the middle of the token secret, I kept always checking the first 6-8 characters then thinking it must be correct. I'll go find a dark corner to sit in shame for such a basic mistake. No idea how I managed to get that incorrect character in there...