key_id and private_key, signing happens automatically on every REST call and WebSocket connection — you do not need to construct signatures manually. This page documents the canonical string format and signing flow for teams building custom integrations, verifying signatures in tests, or implementing the protocol in another language.
If you are using the Python SDK, you do not need to implement signing. Pass
key_id and private_key to the Molecule constructor and the SDK handles all signing transparently. The information below is intended for custom integrations or independent verification.How the SDK Signs Requests
When you construct aMolecule client with credentials, the SDK creates an internal Signer instance that holds a reference to your Ed25519 SigningKey. The private key material never leaves your process.
- Builds a canonical string from the request method, path, query parameters, timestamp, and body hash
- Signs the canonical string bytes with your Ed25519 private key
- Attaches three headers to the HTTP request
The server looks up the registered public key for the given
key_id, verifies the signature against the canonical string it reconstructs from the incoming request, and rejects any request whose timestamp falls outside a 30-second replay window.
REST Canonical String
The canonical string is a newline-delimited sequence of five fields, encoded as UTF-8 bytes before signing:Field definitions
Field definitions
METHOD — HTTP method in uppercase:
GET, POST, PUT, PATCH, or DELETE.PATH — The request path including the leading slash, without query string or fragment. For example, /v1/orders.SORTED_QUERY — All non-null query parameters sorted first by key, then by value, URL-encoded. Boolean values are serialised as true or false. If there are no query parameters, this field is an empty string (the newline is still present).TIMESTAMP_MS — The current Unix time in milliseconds as a decimal string. The value in this field must match X-Molecule-Timestamp exactly.hex(sha256(BODY)) — Lowercase hex-encoded SHA-256 digest of the raw request body bytes. For requests with no body (GET, DELETE), use the SHA-256 of the empty string: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.POST /v1/orders call with a query parameter and a JSON body:
WebSocket Canonical String
WebSocket connections use a similar but shorter canonical string. Signing parameters are appended to the WebSocket URL as query parameters rather than sent as headers.SORTED_QUERY field excludes the signing parameters themselves (key_id, ts, sig, access_token) — they are added to the URL after signing. All other query parameters follow the same sort and encoding rules as the REST canonical string. There is no body field for WebSocket connections.
After signing, three parameters are appended to the WebSocket URL:
Use
client.ws.orders() (or any other WebSocket helper) to get a fully signed URL ready to connect:
client.iter_ws to connect and consume messages without managing the WebSocket lifecycle directly:
Replay Protection
The server enforces a 30-second replay window on all signed requests. Any request whoseX-Molecule-Timestamp (or ts query parameter for WebSocket) is more than 30 seconds in the past is rejected with a 401 stale_timestamp error.
Private Key Formats
Theload_private_key function in molecule.auth accepts the following formats for the 32-byte Ed25519 seed:
Base64 encoding of the 32-byte seed is the canonical format used for environment variables and configuration files.
