-1022 Invalid Signature only on certain endpoints? Signature is valid

I have been working with the API on several endpoints and have noticed a strange which leads me to believe something else is manifesting itself as an “Invalid Signature” error… wondering if anyone else has experienced this.

If I call the /accountSnapshot api, it returns data no problem.
However, if I call the /getUserAsset api it returns

{
    "code": -1022,
    "msg": "Signature for this request is not valid."
}

I know the function to generate the signature is valid because it works for the first call and I tested it against the example in the documentation. Additionally, both of these endpoints only require the timestamp parameter- so there are literally no other differences in the structure of the request. The only thing different about these two endpoints that I can see is that the one that works is [GET] and the other is [POST].

It’s hard to guess what might be wrong. Can you put a curl which shows the way you are submitting the request?
What to check:

  1. ntp - is your current time up-to-date? (even 1 second offset is problematic)
  2. Is your URL something like this: /sapi/v1/asset/getUserAsset?timestamp=$TIMESTAMP&signature=$SIGNATURE - signature must be at the end!
  3. Is your network fast enough? Maybe the signature times out? You can add recvWindow=10000 before timestamp to test that out.
  4. Have you double checked the API_KEY / API_SECRET and that you haven’t misplaced them?

Other than that - I don’t know and there is very little to go on with, because there is no details.

It is working fine for me in Postman, so it’s probably something on your end.

So it started working literally with no changes from me (via postman), so I am convinced there is some condition masking itself. I guess the point you make in #1 (and possibly #3) is a valid consideration that could only manifest itself in an occasional situation if I wasn’t in sync. I have Gigabit fiber so I highly doubt that could be an issue, but during an isolated period I guess anything is possible. Points #2 and #4 are definitely fine. Here is my hashing function for postman in case anyone else can get any benefit from it:

I actually put it as a global variable so that each request has it available to be called.

postman.setGlobalVariable("binance__generateSignatureFunction", (payload, secretKey) => {
    let hash = CryptoJS.HmacSHA256(payload, secretKey);

    return CryptoJS.enc.Hex.stringify(hash);
});

Inside the request’s pre-request script:

let generateSignature = eval(postman.getGlobalVariable("binance__generateSignatureFunction"));

Here’s my pre-request script. I have configured this at a collection level and then all requests inherit it to avoid having to repeat myself.

You need to set the Authorization to type API key:

Then paste this into the Pre-request script section :

const ts = Date.now();
pm.environment.set("timestamp", ts);

const apiSecret = pm.environment.get("API_SECRET");

sign(getBodyPayload());

function getBodyPayload()
{
    const paramsObject = new Map()

    const parseParam = (param) => {
        if (param.key != 'signature' && 
            param.key != 'timestamp' && 
            !is_empty(param.value) &&
            !is_disabled(param.disabled)) 
        {
            //console.log({key: `${param.key}`, value: `${encodeURIComponent(param.value)}`});
            paramsObject.set(param.key, encodeURIComponent(pm.variables.replaceIn(param.value)));
            //pm.environment.set(param.key, encodeURIComponent(param.value));
        }
    };

    // go through query parameters
    console.log(`Query string: ${pm.request.url.query}`);
    pm.request.url.query.map(parseParam);

    // go through body payloads.
    if(pm.request.body.mode == "urlencoded") {
        console.log(`Body payload: ${pm.request.body.urlencoded}`)
        pm.request.body.urlencoded.each(parseParam);
    }

    // Must come last -- order is important.
    paramsObject.set('timestamp', ts);

    return paramsObject
}

function sign(payload)
{
    console.log(`Populated params: ${payload.size}`);
    //paramsObject.forEach((d) => console.log(d));

    let signature = null;

    if (apiSecret) {
        let queryString = "";
        payload.forEach((value, key) => queryString += (key + "=" + value + "&") );
        queryString = queryString.substring(0,queryString.length - 1);
        console.log(`Singing query string: ${queryString}`);
        signature = CryptoJS.HmacSHA256(queryString, apiSecret).toString();
        console.log(`Generated signature: ${signature}`);
        pm.environment.set("signature", signature);
        payload.set("signature", signature)
    } else {
        console.warn("No API SECRET found!");
    }

    return signature;
}

function is_disabled(str) { return str == true; }

function is_empty(str) {
    if (typeof str == 'undefined' ||
        !str ||
        str.length === 0 ||
        str === "" ||
        !/[^\\s]/.test(str) ||
        /^\\s*$/.test(str) ||
        str.replace(/\\s/g, "") === "")
    {
        return true;
    }
    else
    {
        return false;
    }
}

function queryStringToObject(queryString) {
  let obj = {}
  if(queryString) {
      console.log(queryString);
    queryString.split('&').map((item) => {
      const [ k, v ] = item.split('=')
      v ? obj[k] = v : null
    })
  }
  return obj
}

Once you have this you need to configure your environment settings with the following variables (some of these are empty placeholders that are auto-populated during the pre-script execution):

Finally in the request settings:

You should also setup the Authorization to Inherit from parent.

This would then work for any request. The only rules are:

  1. recvWindow parameter needs to be before timestamp.
  2. timestamp parameter needs to be before signature.
  3. signature parameter must ALWAYS be the last parameter (as shown in the screenshot)

The code you posted looks okay to me, but I don’t know what the payload is (I am assuming it’s just timestamp=unix timestamp with millis (check this or use new Date().now() in your js). I had a lot of issues to get it to work as expected when I first started. What helped me was the example here:

https://binance-docs.github.io/apidocs/spot/en/#signed-trade-user_data-and-margin-endpoint-security

You can search for HMAC SHA256 signature - they have an example with openssl and a fake key/secret. If your code can produce the same signature then it is working as expected – otherwise you have an issue somewhere in the code (i.e. maybe concurrency, object mutability or something in that manner)

1 Like