3. Autenticación: firmar cada petición con HMAC
Nivel 13: Conectar con Brokers Reales
Conectarse a una API de trading con Python consiste, típicamente, en usar la librería requests para hacer peticiones HTTP al endpoint de la API. Lo primero es obtener las credenciales (clave de API, secreto, y en algunos exchanges una passphrase) desde la plataforma. Consulta siempre la documentación oficial del bróker para los detalles concretos de endpoints, autenticación, límites de peticiones y formatos de datos.
En Coinbase Pro entras en la sección de API de tu cuenta y creas una nueva clave. Obtendrás tres valores: API key, API secret y API passphrase. Con ellos autenticas cada petición.
Como ya viste en niveles anteriores, esas credenciales nunca se escriben dentro del código. Se leen de variables de entorno, y el fichero que las define queda fuera del control de versiones:
import os
import time
import hmac
import hashlib
import base64
import json
import requests
# Definidas fuera del codigo, por ejemplo en un fichero .env que NO se sube a git:
# export CB_API_KEY='tu_clave_aqui'
# export CB_API_SECRET='tu_secreto_aqui'
# export CB_API_PASS='tu_passphrase_aqui'
API_KEY = os.environ['CB_API_KEY']
API_SECRET = os.environ['CB_API_SECRET']
API_PASS = os.environ['CB_API_PASS']Si la variable no existe, os.environ[...] lanza un KeyError y el programa muere al arrancar. Eso es exactamente lo que quieres: es mucho mejor fallar al inicio que descubrir a mitad de sesión que las credenciales estaban vacías.
Cómo funciona la firma
La clave secreta no viaja nunca por la red. Lo que viaja es una *firma*: un resumen criptográfico calculado con el secreto sobre los datos exactos de esta petición. El servidor, que conoce el mismo secreto, repite el cálculo y compara. Si coincide, la petición es auténtica y nadie la ha modificado por el camino.
# Marca de tiempo en segundos
timestamp = str(time.time())
method = 'POST'
request_path = '/orders'
body_json = json.dumps(body)
# El mensaje a firmar: timestamp + metodo + ruta + cuerpo, en este orden exacto
message = timestamp + method + request_path + body_json
# El secreto de Coinbase viene en base64: hay que decodificarlo antes de usarlo
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'
}Hay tres detalles que provocan el 90% de los errores 401 de los principiantes:
- El orden y el contenido del mensaje son literales. Si firmas
body_jsonpero luego envías un JSON regenerado con otro orden de claves o distintos espacios, la firma no coincidirá. Serializa una sola vez y envía exactamente esa cadena. - El timestamp importa. Sirve para evitar ataques de repetición: el servidor rechaza peticiones con una marca de tiempo demasiado alejada de la suya (típicamente unos segundos). Si el reloj de tu servidor va desfasado, todas tus peticiones fallarán con error de autenticación aunque el código sea correcto. Sincroniza el reloj con NTP.
- El secreto se decodifica de base64 antes de usarlo como clave. Usar la cadena tal cual produce una firma distinta.
Y una última medida que es puro sentido común operativo: al crear la clave, concédele solo permisos de consulta y de trading, nunca permiso de retirada de fondos, y restringe su uso a la IP de tu servidor si el exchange lo permite. Si algún día esa clave se filtra, la diferencia entre esos permisos y los completos es la diferencia entre un susto y una pérdida total.