Skip to main content
Every request to the Molecule API is authenticated with an Ed25519 digital signature. When you initialise the SDK with a 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 a Molecule 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.
For every outgoing REST request, the SDK:
  1. Builds a canonical string from the request method, path, query parameters, timestamp, and body hash
  2. Signs the canonical string bytes with your Ed25519 private key
  3. 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:
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.
The following example shows the canonical string for a POST /v1/orders call with a query parameter and a JSON body:
To produce this manually in Python:

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.
The 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:
You can also use client.iter_ws to connect and consume messages without managing the WebSocket lifecycle directly:
The canonical string for this connection (before signing) is:

Replay Protection

The server enforces a 30-second replay window on all signed requests. Any request whose X-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

The load_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.
Store your private key in an environment variable or secrets manager — never hardcode it in source code or commit it to version control. The key authorises all order activity on your account.