POST order , {"message":"invalid signature"}? , python

I’m attempting to create a sell order based on this documentation. Create a new order

It looks like some variables are missing though and I’m too new to coding to figure out what’s required/missing. Some help would be appreciated.

If I copy/paste from the documentation I get an Unauthorized error.

I updated the headers section to be like this…

headers = {
‘Accept’: ‘application/json’,
‘Content-Type’: ‘application/json’,
‘CB-ACCESS-KEY’: cb_api_key,
‘CB-ACCESS-PASSPHRASE’: cb_access_passphrase,
‘CB-ACCESS-SIGN’: signature,
‘CB-ACCESS-TIMESTAMP’: cb_access_timestamp
}

Now I get an invalid signature error.

Try this:

import time
import base64
import hmac
import hashlib
import requests
import json

cbTime = str(int(time.time()))
cbPass = ‘x’
cbKey = ‘x’
cbSecret = ‘x==’
cbMeth = ‘POST’
cbPair = ‘X-Y’
cbPath = ‘/orders’
payload = {
‘profile_id’: cbKey,
‘product_id’: cbPair,
‘type’: ‘limit’,
‘side’: ‘sell’,
‘price’: ‘x’,
‘size’: ‘x’
}
body = json.dumps(payload)
cbMsg = ‘{}{}{}{}’.format(cbTime,cbMeth,cbPath,body)

key = base64.b64decode(cbSecret)
msg = cbMsg.encode(‘utf-8’)
ash = hmac.digest(key, msg, hashlib.sha256)
sig = base64.b64encode(ash).decode(‘utf-8’)

headers = {
‘Content-Type’: ‘application/json’,
‘CB-ACCESS-TIMESTAMP’: cbTime,
‘CB-ACCESS-KEY’: cbKey,
‘CB-ACCESS-PASSPHRASE’: cbPass,
‘CB-ACCESS-SIGN’: sig
}

url = ‘https://api.exchange.coinbase.com/orders
r = requests.post(url, body, headers=headers)

print()
print(cbMsg)
print()
print(sig)
print()
print(r.text)
print()

You have your secret key and api key positions switched. Please see the proper REST signature thread.

That’s a working code, so nothing is improperly switched.

What’s confusing about my code is the two variables cbKey and key. The variable key is just a feeder for the next line, so it could be renamed to anything less confusing.

Thank you. I appreciate the hand holding. That code did work and I think I’m learning from this.

@shurr This code cannot work. You have your profile id set to your key. You will find that endpoints that do not require authentication will accept invalid signatures, just like an unlocked door will open to any key you put in it.

@cleggink Well, you’re right that profile_id has the wrong value. But what I found out today was that profile_id isn’t even needed in the body for an order to be placed. What’s needed are the various requested strings in the header.

apologies for hijacking the thread, I’m a new member and apparently I can only reply 3 times to a specific thread and can’t post one of my own yet.

I’m facing the same kind of issue in node js.

I have used @larry.kubin code for GET and works perfectly fine with no issues. If I do a manual transaction it shows up and pulls all the details just fine.
I’m talking about the production api not sandbox.
Which by the way I tried both:

api.exchange.coinbase.com
and
api.pro.coinbase.com

same result in both cases.

When switching to POST I get:

statusCode: 401
{"message":"invalid signature"}

so… I did a little bit more debugging…
as I said the GET request returns a 200 and if I manually do some orders I can pull them just fine.
If I do a POST and leave the body blank like in the GET request.
I’m getting:

statusCode: 400
{"message":"product_id is not a valid product"}

which means that it connects to the api but it is expecting the specific body parameters.

this is how I’m building the body. so it’s either my syntax or I’m missing something:

var body = JSON.stringify({
    product_id: 'BTC-USD',
    side: 'buy',
    type: 'limit',
    size: '0.0001',
    price: '44415.51'
});

and this is the way I’m building the message:

var message = cb_access_timestamp + method + path + body;

is there a specific order in which the pairs need to be included?
as soon as I construct the body like this I get the:

statusCode: 401
{"message":"invalid signature"}

for path I’m using:

var path = '/orders';

and this is what I use for signing:

var key = CryptoJS.enc.Base64.parse(cb_secret);
var hash = CryptoJS.HmacSHA256(message, key);
var sign = hash.toString(CryptoJS.enc.Base64);

The entire code is below:

const config = require('dotenv').config();
var CryptoJS = require('crypto-js');
const https = require('https');

// current unix timestamp in seconds (note Date.now() returns milliseconds)
var cb_access_timestamp = Date.now() / 1000;

// obtained API key, secret, and passphrase from https://pro.coinbase.com/profile/api
var cb_access_passphrase = process.env.cb_access_passphrase;
var cb_api_key = process.env.cb_api_key;
var cb_secret = process.env.cb_secret;
var path = '/orders';
//var path = '/trade';
var body = JSON.stringify({
    product_id: 'BTC-USD',
    side: 'buy',
    type: 'limit',
    size: '0.0001',
    price: '44415.51'
}); 
var method = 'POST';

// create the prehash string by concatenating required parts
var message = cb_access_timestamp + method + path + body;

var key = CryptoJS.enc.Base64.parse(cb_secret);
var hash = CryptoJS.HmacSHA256(message, key);
var sign = hash.toString(CryptoJS.enc.Base64);

const options = {
  hostname: 'api.exchange.coinbase.com',
  //hostname: 'api.pro.coinbase.com',
  port: 443,
  path: path,
  method: method,
  headers: {
    'Content-Type': 'application/json',
    'Content-Length': 0,
    'User-Agent': 'MyTradingBot',
    'CB-ACCESS-KEY': cb_api_key,
    'CB-ACCESS-PASSPHRASE': cb_access_passphrase,
    'CB-ACCESS-SIGN': sign,
    'CB-ACCESS-TIMESTAMP': cb_access_timestamp
  }
}

const req = https.request(options, res => {
    //console.log(cb_secret);
    console.log(`statusCode: ${res.statusCode}`)
    
    res.on('data', d => {
        process.stdout.write(d)
    })
})

req.on('error', error => {
  console.error(error)
})

req.end()

I might be able to help you if you provide the exact message string that you’re feeding into the functions to make the sig. I know you’re using:

var message = cb_access_timestamp + method + path + body;

but I want to see the actual msg string, not this concatenation statement.

wait, so you actually wanna see the hashed secret key?

What I can confirm is that using the crypto pkg vs the CryptoJS the results are completely different hashes.

var key = Buffer.from(cb_secret, 'base64');
var hmac = crypto.createHmac('sha256', key);
var sign = hmac.update(message).digest('base64'); */

sign returns something like this:

N19.........fQ=

while:

var key = CryptoJS.enc.Base64.parse(cb_secret);
var hash = CryptoJS.HmacSHA256(message, key);
var sign = hash.toString(CryptoJS.enc.Base64);

sign returns something like this:

Y6P.........nld=

completely different hashes

— Update Note —
disregard this… since I’m such a noob. :slight_smile:
The hash being based on a timestamp will obviously be different every single time

No. That’s not what I wrote. I wrote that I wanted to see the form of a message string that you feed into the function.

@shurr sorry, didn’t mean to come off like that… I really am just dabbling in this for the very first time. I have built a couple of scripts before but nothing as complex as this… and the doc for the api is lacking big time.
that being said… the code I posted above is my entire code.
the variables:

var cb_access_passphrase = process.env.cb_access_passphrase;
var cb_api_key = process.env.cb_api_key;
var cb_secret = process.env.cb_secret;

are defined in a .env file
I can test and console.log() them just fine
timestamp is:

var cb_access_timestamp = Date.now() / 1000;

which returns something like this:

1649197824.804

then, I build the message like so:

var message = cb_access_timestamp + method + path + body;

where method is:

var method = 'POST';

path is:

var path = '/orders';

and body is:

var body = JSON.stringify({
    "product_id":"BTC-USD",
    "side":"buy",
    "type":"limit",
    "size":"0.0001",
    "price":"44415.51"
});

then I use either the crypto pkg to build the signature or the CriptoJS
using crypto pkg:

var key = Buffer.from(cb_secret, 'base64');
var hmac = crypto.createHmac('sha256', key);
var sign = hmac.update(message).digest('base64');

using CryptoJS:

var key = CryptoJS.enc.Base64.parse(cb_secret);
var hash = CryptoJS.HmacSHA256(message, key);
var sign = hash.toString(CryptoJS.enc.Base64);

and then I pass that to the headers section:

const options = {
  hostname: 'api.exchange.coinbase.com',
  //hostname: 'api.pro.coinbase.com',
  port: 443,
  path: path,
  method: method,
  headers: {
    'Content-Type': 'application/json',
    'Content-Length': 0,
    'User-Agent': 'MyTradingBot',
    'CB-ACCESS-KEY': cb_api_key,
    'CB-ACCESS-PASSPHRASE': cb_access_passphrase,
    'CB-ACCESS-SIGN': sign,
    'CB-ACCESS-TIMESTAMP': cb_access_timestamp
  }
}

and then submit the request:

const req = https.request(options, res => {
    //console.log(cb_access_timestamp);
    console.log(`statusCode: ${res.statusCode}`)
    
    res.on('data', d => {
        process.stdout.write(d)
    })
})

req.on('error', error => {
  console.error(error)
})

I’m sorry if I misunderstood your question… but that’s all I have so far… no other functions/classes/anything.
If I console.log(message) something like this is returned:

1649198401.549POST/orders{"product_id":"BTC-USD","side":"buy","type":"limit","size":"0.0001","price":"44415.51"}

I was just trying to build my very first script for Coinbase and then build on that.

And… I believe I ate up all my posts for the day and don’t feel like hijacking other people’s threads.
I shall be back here tomorrow.
I appreciate all your help and time.

Body looks well-formed. Decimals aren’t needed in the timestamp, but probably don’t affect anything.

Not familiar with the following; they might not be needed and they might or might not cause a problem:

But more importantly, you might need to insert body in options. I think you have to send body along with the other elements.

Good luck.

not sure either… they were included in the original GET example that I got working, so figured they might not hurt leaving them there.
btw, I guess I got my privileges upgraded and can post more. I’ll start a new thread for this specific issue. maybe one of the devs is kind enough to throw me some tips

Your keys are still in the wrong place. Api key does not go in the header, it goes in your encryption function.

wait, so you mean instead of:

var key = Buffer.from(cb_secret, 'base64');

it should be:

var key = Buffer.from(cb_api_key, 'base64');

???

And then what should I put here?

headers: {
...
...
'CB-ACCESS-KEY': cb_api_key,
...
}

if I do that and switch to:

var key = Buffer.from(cb_api_key, 'base64');

and:

headers: {
...
...
'CB-ACCESS-KEY': cb_secret,
...
}

I get:

statusCode: 401
{"message":"Invalid API Key"}

Please see my thread Proper REST signatures for the entire walkthru for signature related issues, this is covered there.

Thank you so much for your example you really saved my butt. I could not figure out where I was going wrong. I’m unbelivably thankful you were able to decipher the coinbase documentation. I couldn’t to save my life. I was able to comprehend Kraken’s docs with relative ease but not coinbase.

Blockquote