I am implementing Coinbase's exchange API using custom auth in requests-python. The following code works with all the (authenticated) GET-based calls, but fails for all the authenticated POST-based calls (I haven't tried with DELETE or UPDATE verbs). I don't understand why the signature wouldn't work for both, because the payload is timestamp + method + path
for GETs and timestamp + method + path + body
for PUTs, so custom auth seems correct. Something is going wrong with adding the body and changing GET to POST. Thanks!
You can get your API keys for trying it out here: https://gdax.com/settings
import json, hmac, hashlib, time, requests, base64
from requests.auth import AuthBase
class CoinbaseAuth(AuthBase):
SIGNATURE_HTTP_HEADER = 'CB-ACCESS-SIGN'
TIMESTAMP_HTTP_HEADER = 'CB-ACCESS-TIMESTAMP'
KEY_HTTP_HEADER = 'CB-ACCESS-KEY'
PASSPHRASE_HTTP_HEADER = 'CB-ACCESS-PASSPHRASE'
def __init__(self, api_key, secret_key, passphrase):
self.api_key = api_key
self.secret_key = secret_key
self.passphrase = passphrase
def __call__(self, request):
#Add headers
request.headers[CoinbaseAuth.KEY_HTTP_HEADER] = self.api_key
request.headers[CoinbaseAuth.PASSPHRASE_HTTP_HEADER] = self.passphrase
timestamp = str(time.time())
request.headers[CoinbaseAuth.TIMESTAMP_HTTP_HEADER] = timestamp
#add signature
method = request.method
path = request.path_url
content = request.body
message = timestamp + method + path
if content:
message += content
hmac_key = base64.b64decode(self.secret_key)
sig = hmac.new(hmac_key, message, hashlib.sha256)
sig_b64 = sig.digest().encode("base64").rstrip("\n")
#Add signature header
request.headers[CoinbaseAuth.SIGNATURE_HTTP_HEADER] = sig_b64
return request
#Get your keys here: https://gdax.com/settings
key = 'KEY GOES HERE'
secret = 'SECRET GOES HERE'
passphrase = 'PASSPHRASE GOES HERE'
api_url = 'https://api.gdax.com:443/'
auth = CoinbaseAuth(API_KEY, API_SECRET, API_PASS)
#GETs work, shows account balances
r = requests.get(api_url + 'accounts', auth=auth)
print r.json()
#POSTs fail: {message: 'invalid signature'}
order = {}
order['size'] = 0.01
order['price'] = 100
order['side'] = 'buy'
order['product_id'] = 'BTC-USD'
r = requests.post(api_url + 'orders', data=json.dumps(order), auth=auth)
print r.json()
And the output:
GET call: 200: [{u'available': .......}]
POST call: 400: {u'message': u'invalid signature'}
EDIT: POSTing 'a' instead of valid JSON-encoded data results in the same signature error (rather than a JSON decoding error from the server), so I don't think it is the way I'm forming the data. Notably, if I omit the body -- request.post(..., data='',...)
--- the server responds appropriately with {u'message': u'Missing product_id'}
.