Current section
Files
Jump to
Current section
Files
lib/ccxt/ws/auth/inline_subscribe.ex
defmodule CCXT.WS.Auth.InlineSubscribe do
@moduledoc """
Inline Subscribe authentication pattern (Coinbase style).
This pattern includes authentication data directly in subscribe messages
rather than sending a separate authentication message.
## Flow
1. Connect to WebSocket (no auth message)
2. Include auth fields (key, timestamp, signature, passphrase) in each private subscribe
3. Auth is per-subscription, not per-connection
## Example Subscribe with Auth (Coinbase)
%{
"type" => "subscribe",
"product_ids" => ["BTC-USD"],
"channels" => ["level2"],
"key" => "api_key_here",
"timestamp" => "1699999999",
"signature" => "base64_signature",
"passphrase" => "my_passphrase"
}
## Payload Format (matches CCXT's authenticate())
timestamp + "GET" + "/users/self/verify"
The secret is base64-decoded before signing, and the signature is base64-encoded.
"""
@behaviour CCXT.WS.Auth.Behaviour
alias CCXT.Signing
@verify_path "/users/self/verify"
@impl true
def pre_auth(_credentials, _config, _opts) do
{:ok, %{}}
end
@impl true
def build_auth_message(_credentials, _config, _opts) do
# No standalone auth message - auth is inline with subscribe
:no_message
end
@impl true
def handle_auth_response(_response, _state) do
# No standalone auth response
:ok
end
@doc """
Builds auth data to merge into subscribe messages.
Matches CCXT's `coinbaseexchange.authenticate()`:
- Payload: `timestamp + "GET" + "/users/self/verify"`
- Secret: base64-decoded before HMAC-SHA256
- Signature: base64-encoded
- Fields: `key`, `timestamp`, `signature`, `passphrase`
"""
@impl true
def build_subscribe_auth(credentials, _config, _channel, _symbols) do
api_key = credentials.api_key
secret = credentials.secret
passphrase = credentials.password
timestamp = to_string(Signing.timestamp_seconds())
payload = timestamp <> "GET" <> @verify_path
# Secret is base64-encoded; decode before signing
secret_binary = Base.decode64!(secret)
signature_raw = Signing.hmac_sha256(payload, secret_binary)
signature = Base.encode64(signature_raw)
result = %{
"key" => api_key,
"timestamp" => timestamp,
"signature" => signature
}
if passphrase, do: Map.put(result, "passphrase", passphrase), else: result
end
end