Websocket Ticker Authentication Error (python)

OK, I am having trouble getting ticker prices with the same Authorization error that I think many people seemed to suffer from. I have been through every thread here related to this (python and otherwise) and I can’t make my code work.

I have a couple of preliminary questions in case I am missing something basic:

  1. Do I have to authorize my development machine before the API will authorize me? (I have done this - at least I am logged on to coinbase.com from my dev machine and it is in my authorized devices)
  2. Do I need to use a REST API to somehow get a session or user id key before I can get messages from the websocket? (If yes, how? And then, how do I use it?)
  3. I have an API key I generated 6 months ago that I have been using for the v2 API, do I need to get a new one for Advanced Trade? (I have generated a new one to be safe, but it takes 48 hours before I can try it out)

Here is my code, having followed the websocket documentation:

def add_signature_ws(message:dict, secret:str):
    nonce = int(time.time() * 1e6)
    to_sign = f"{nonce}{message['channel']}{','.join(message['product_ids'])}"
    signature = hmac.new(secret.encode('utf-8'), to_sign.encode('utf-8'), hashlib.sha256).hexdigest()
    message['signature'] = signature
    message['timestamp'] = str(nonce)

    return message

def batch_ticker_price():
    ticker_batch = {
        "type": "subscribe",
        "product_ids": [
            "ETH-USD"
        ],
        "channel": "ticker_batch",
        'api_key' : SecretManager.get('COINBASE_API_KEY')
    }
    ws = websocket.create_connection("wss://advanced-trade-ws.coinbase.com")
    msg = add_signature_ws(ticker_batch, SecretManager.get('COINBASE_API_SECRET'))
    ws.send(json.dumps(msg))
    data = json.loads(ws.recv())

what I sign looks like this:

1673924763477896ticker_batchETH-USD

the contents of the signed message look like this:

{'type': 'subscribe', 'product_ids': ['ETH-USD'], 'channel': 'ticker_batch', 'api_key': '---REDACTED---', 'signature': '3995240d5a6e8174df5e45734955b109ab1bfd77bff5f18d1922f1e3a44d0143', 'timestamp': '1673924763477896'}

what I get back is (always) this:

{'type': 'error', 'message': 'authentication failure'}

My SecretManager is giving me the right values for my API key and Secret and the keys are appropriately scoped (wide open).

I have tried many, many variants of this, attempting to port code from Javascript, Java, R, C++, C#, but I just can’t get it to work. I have tried different encodings and digests of the signature, but no joy.

I am tearing my hair out and hoping it’s something really stupid.

Sure enough it was something really stupid…

nonce = int(time.time() * 1e6)

changed to…

nonce = int(time.time())

here is Python code that works, and uses asyncio to yield prices when they arrive:

def add_signature_ws(message:dict, secret:str):
    nonce = int(time.time())
    to_sign = f"{nonce}{message['channel']}{','.join(message['product_ids'])}"
    signature = hmac.new(secret.encode('utf-8'), to_sign.encode('utf-8'), hashlib.sha256).hexdigest()
    message['signature'] = signature
    message['timestamp'] = str(nonce)

    return message

async def yield_prices(sym:str):

    product_ids = [f"{sym}-USD"]
    ticker_batch = {
        "type": "subscribe",
        "product_ids": product_ids,
        "channel": "ticker_batch",
        'api_key' : SecretManager.get('COINBASE_API_KEY')
    }
    message = add_signature_ws(ticker_batch, SecretManager.get('COINBASE_API_SECRET'))

    ws = await websockets.connect(COINBASE_WS_API)
    await ws.send(json.dumps(message))

    while True:
        response = await ws.recv()
        data = json.loads(response)
        logging.info(data)
        if data['channel'] == 'ticker_batch':
            yield data

Hopefully helps someone in their Python journey

Nice catch @simon.palmer im actually having the same issue. Thanks for this