Signs a request with credentials the host has already obtained. Internal.
This package does not handle authentication
It signs. The host implements authentication and holds the credentials. This module is handed them per call, signs one request, and keeps nothing.
Robinhood's scheme
Three headers:
x-api-key: <api key>
x-timestamp: <unix seconds>
x-signature: base64(ed25519_sign(payload, private_key))where the signed payload is a plain concatenation, in this order and with no separators:
api_key <> timestamp <> path <> method <> bodyFour details in that line are easy to get wrong, and each produces the same unhelpful 401:
pathincludes the query string./api/v1/crypto/marketdata/best_bid_ask/?symbol=BTC-USD, not the path alone. A signature over the bare path fails on every request that filters.methodis uppercase.bodyis the empty string for a GET, not omitted — the concatenation still has a slot for it.timestampis seconds as a string, not milliseconds.
The key is a seed, not a signing key
Robinhood issues a base64-encoded 32-byte Ed25519 seed. NaCl-style, the seed deterministically derives the signing key. Handing the seed to a signer that expects a full 64-byte secret key produces a valid-looking signature that the venue rejects — so the derivation happens here, once, from the venue's own format.
Summary
Types
Credentials the host obtained. Signed with, and not kept.
Functions
Headers for a signed request.
The signed payload, exposed because its ordering is the whole scheme.
Types
Functions
@spec headers(String.t(), String.t(), String.t(), credentials(), keyword()) :: {:ok, [{String.t(), String.t()}]} | {:error, term()}
Headers for a signed request.
path must already include the query string, because the venue signs it.
Returns {:error, {:missing_credentials, :robinhood}} rather than signing with a partial
credential, and {:error, {:invalid_private_key, reason}} when the key is not the
base64 32-byte seed the venue issues — both of which are clearer than the 401 they would
otherwise become.
The signed payload, exposed because its ordering is the whole scheme.
A signature is opaque; the string it was taken over is not, and it is the thing worth asserting.