Quant500 Academy

3. Authentication: signing every request with HMAC

Level 13: Connecting to Real Brokers

Connecting to a trading API with Python typically consists of using the requests library to make HTTP requests to the API endpoint. The first thing you need is credentials (API key, secret, and on some exchanges a passphrase) obtained from the platform. Always consult the broker's official documentation for the specific details of endpoints, authentication, rate limits and data formats.

On Coinbase Pro you go to the API section of your account and create a new key. You will get three values: API key, API secret and API passphrase. With them you authenticate every request.

As you saw in earlier levels, those credentials are never written inside the code. They are read from environment variables, and the file that defines them stays out of version control:

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

# Defined outside the code, for example in a .env file that is NOT pushed to git:
#   export CB_API_KEY='your_key_here'
#   export CB_API_SECRET='your_secret_here'
#   export CB_API_PASS='your_passphrase_here'
API_KEY = os.environ['CB_API_KEY']
API_SECRET = os.environ['CB_API_SECRET']
API_PASS = os.environ['CB_API_PASS']

If the variable does not exist, os.environ[...] raises a KeyError and the program dies on startup. That is exactly what you want: it is far better to fail at launch than to discover mid-session that the credentials were empty.

How the signature works

The secret key never travels across the network. What travels is a *signature*: a cryptographic digest computed with the secret over the exact data of this request. The server, which knows the same secret, repeats the computation and compares. If they match, the request is authentic and nobody has modified it along the way.

# Timestamp in seconds
timestamp = str(time.time())

method = 'POST'
request_path = '/orders'
body_json = json.dumps(body)

# The message to sign: timestamp + method + path + body, in this exact order
message = timestamp + method + request_path + body_json

# The Coinbase secret comes base64-encoded: it must be decoded before use
hmac_key = base64.b64decode(API_SECRET)
signature = hmac.new(hmac_key, message.encode('utf-8'), hashlib.sha256)
signature_b64 = base64.b64encode(signature.digest()).decode('utf-8')

headers = {
    'CB-ACCESS-KEY': API_KEY,
    'CB-ACCESS-PASSPHRASE': API_PASS,
    'CB-ACCESS-SIGN': signature_b64,
    'CB-ACCESS-TIMESTAMP': timestamp,
    'Content-Type': 'application/json'
}

There are three details that cause 90% of beginners' 401 errors:

And one last measure that is pure operational common sense: when you create the key, grant it only read and trading permissions, never withdrawal permission, and restrict its use to your server's IP if the exchange allows it. If that key ever leaks, the difference between those permissions and full ones is the difference between a scare and a total loss.

The rest of Quant500

What you are learning, applied to the index every day:

The model portfolio, weekly in your inbox

Every Saturday, its week in one minute: how it went, the best and the worst, and which insiders bought with their own money. Plus, the day the model is rebuilt, which stocks come in and which go out. A simulation, not advice. At most one email a week, no ads, no account; one-click unsubscribe. Made by one person: Pol Gracia.

A model portfolio with simulated results, not a personalized recommendation. Your email is used for this only: privacy.

Behind the Academy and the rest of Quant500 there is one person: Pol Gracia Serrahima.