Advanced Trade WebSocket Authentication
This guide explains how to authenticate requests to the Advanced Trade WebSocket API server channels. It assumes that you have already created API keys on the Coinbase Developer Platform.
Sending Messages with API Keys
Making Requests
Use the code samples below to generate/export a JSON Web Token (JWT) and make an authenticated request.
The differences between the code snippets for WebSockets (below) and REST calls are:
- WebSocket JWTs are not built with a request method or request path.
Generating a JWT
Regardless of which code snippet you use, follow these steps:
- Replace
key name
andkey secret
with your key name and private key.key secret
is a multi-line key and newlines must be preserved to properly parse the key. Do this on one line with\n
escaped newlines, or with a multi-line string. - Run the generation script that prints the command
export JWT=...
. - Run the generated command to save your JWT.
Your JWT expires after 2 minutes, after which all requests are unauthenticated.
Code samples
The easiest way to generate a JWT is to use the built-in functions in our Python SDK as described below. Otherwise, use the code samples below to generate/export a JWT and make an authenticated request.
- Python SDK
- Python
- Go
- JavaScript
-
Install the SDK.
pip3 install coinbase-advanced-py
-
In the console, run:
python main.py
(or whatever your file name is). -
Set the JWT to that output, or export the JWT to the environment with
eval $(python main.py)
.
from coinbase import jwt_generator
api_key = "organizations/{org_id}/apiKeys/{key_id}"
api_secret = "-----BEGIN EC PRIVATE KEY-----\nYOUR PRIVATE KEY\n-----END EC PRIVATE KEY-----\n"
def main():
jwt_token = jwt_generator.build_ws_jwt(api_key, api_secret)
print(f"export JWT={jwt_token}")
if __name__ == "__main__":
main()
- Install dependencies PyJWT and cryptography.
pip install PyJWT
pip install cryptography - In the console, run:
python main.py
(or whatever your file name is). - Set JWT to that output, or export the JWT to the environment with
eval $(python main.py)
.
import jwt
from cryptography.hazmat.primitives import serialization
import time
import secrets
key_name = "organizations/{org_id}/apiKeys/{key_id}"
key_secret = "-----BEGIN EC PRIVATE KEY-----\nYOUR PRIVATE KEY\n-----END EC PRIVATE KEY-----\n"
def build_jwt():
private_key_bytes = key_secret.encode('utf-8')
private_key = serialization.load_pem_private_key(private_key_bytes, password=None)
jwt_payload = {
'sub': key_name,
'iss': "coinbase-cloud",
'nbf': int(time.time()),
'exp': int(time.time()) + 120,
}
jwt_token = jwt.encode(
jwt_payload,
private_key,
algorithm='ES256',
headers={'kid': key_name, 'nonce': secrets.token_hex()},
)
return jwt_token
def main():
jwt_token = build_jwt()
print(f"export JWT={jwt_token}")
if __name__ == "__main__":
main()
- Create a new directory and generate a Go file called
main.go
. - Paste the Go snippet below into
main.go
. - Run
go mod init jwt-generator
andgo mod tidy
to generatego.mod
andgo.sum
and manage your dependencies. - In the console, run: go run
main.go
. This outputs the command,export JWT=
. - Set your JWT with that output, or export the JWT to environment with eval $(go run
main.go
).
package main
import (
"crypto/rand"
"crypto/x509"
"encoding/pem"
"fmt"
"math"
"math/big"
"time"
log "github.com/sirupsen/logrus"
"gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
)
const (
keyName = "organizations/{org_id}/apiKeys/{key_id}"
keySecret = "-----BEGIN EC PRIVATE KEY-----\nYOUR PRIVATE KEY\n-----END EC PRIVATE KEY-----\n"
)
type APIKeyClaims struct {
*jwt.Claims
}
func buildJWT() (string, error) {
block, _ := pem.Decode([]byte(keySecret))
if block == nil {
return "", fmt.Errorf("jwt: Could not decode private key")
}
key, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return "", fmt.Errorf("jwt: %w", err)
}
sig, err := jose.NewSigner(
jose.SigningKey{Algorithm: jose.ES256, Key: key},
(&jose.SignerOptions{NonceSource: nonceSource{}}).WithType("JWT").WithHeader("kid", keyName),
)
if err != nil {
return "", fmt.Errorf("jwt: %w", err)
}
cl := &APIKeyClaims{
Claims: &jwt.Claims{
Subject: keyName,
Issuer: "coinbase-cloud",
NotBefore: jwt.NewNumericDate(time.Now()),
Expiry: jwt.NewNumericDate(time.Now().Add(2 * time.Minute)),
},
}
jwtString, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
if err != nil {
return "", fmt.Errorf("jwt: %w", err)
}
return jwtString, nil
}
var max = big.NewInt(math.MaxInt64)
type nonceSource struct{}
func (n nonceSource) Nonce() (string, error) {
r, err := rand.Int(rand.Reader, max)
if err != nil {
return "", err
}
return r.String(), nil
}
func main() {
jwt, err := buildJWT()
if err != nil {
log.Errorf("error building jwt: %v", err)
}
fmt.Println("export JWT=" + jwt)
}
- Install jsonwebtoken.
npm install jsonwebtoken
- In the console, run:
node main.js
(or whatever your file name is). - Set JWT to that output, or export the JWT to environment with
eval $(node main.py)
.
const { sign } = require('jsonwebtoken');
const crypto = require('crypto');
const key_name = 'organizations/{org_id}/apiKeys/{key_id}';
const key_secret = '-----BEGIN EC PRIVATE KEY-----\nYOUR PRIVATE KEY\n-----END EC PRIVATE KEY-----\n';
const algorithm = 'ES256';
const token = sign(
{
iss: 'coinbase-cloud',
nbf: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 120,
sub: key_name,
},
key_secret,
{
algorithm,
header: {
kid: key_name,
nonce: crypto.randomBytes(16).toString('hex'),
},
}
);
console.log('export JWT=' + token);
Sending Messages without API Keys
Subscribing
// Request
// Subscribe to ETH-USD and ETH-EUR with the level2 channel
{
"type": "subscribe",
"product_ids": [
"ETH-USD",
"ETH-EUR"
],
"channel": "level2"
}
Unsubscribing
// Request
{
"type": "unsubscribe",
"product_ids": [
"ETH-USD",
"ETH-EUR"
],
"channel": "level2"
}
Sequence Numbers
Most feed messages contain a sequence number. Sequence numbers are increasing integer values for each product, with each new message being exactly one sequence number greater than the one before it.
Sequence numbers that are greater than one integer value from the previous number indicate that a message has been dropped. Sequence numbers that are less than the previous number can be ignored or represent a message that has arrived out of order.
In either situation you may need to perform logic to make sure your system is in the correct state.
Even though a WebSocket connection is over TCP, the WebSocket servers receive market data in a manner that can result in dropped messages. Your feed consumer should be designed to handle sequence gaps and out of order messages, or should use channels that guarantee delivery of messages.
To guarantee that messages are delivered and your order book is in sync, consider using the level2 channel.
See Also: