I have been struggling with signing requests for private GDAX endpoints for a while. Everything I have tried results in a 400 response with a message of "invalid signature." I have read their documentation on the matter several times, which can be found here. My current code is below. I'm using clj-http
for making requests. I'm using their /time
endpoint response for the timestamp, and I'm using pandect for the sha256 HMAC generation. I've tried converting the secret-decoded
to a string using String.
before passing it to sha256-hmac
. I've also examined the request using clj-http
's debug flag. It looks to me that I am following their directions precisely, but something must be wrong. I've done a lot of online searching before posting here. Any help would be greatly appreciated.
(defn get-time
[]
(-> (str (:api-base-url config) "/time")
(http/get {:as :json})
:body))
(defn- create-signature
([timestamp method path]
(create-signature timestamp method path ""))
([timestamp method path body]
(let [secret-decoded (b64/decode (.getBytes (:api-secret config)))
prehash-string (str timestamp (clojure.string/upper-case method) path body)
hmac (sha256-hmac prehash-string secret-decoded)]
(-> hmac
.getBytes
b64/encode
String.))))
(defn- send-signed-request
[method path & [opts]]
(let [url (str (:api-base-url config) path)
timestamp (long (:epoch (get-time)))
signature (create-signature timestamp method path (:body opts))]
(http/request
(merge {:method method
:url url
:as :json
:headers {"CB-ACCESS-KEY" (:api-key config)
"CB-ACCESS-SIGN" signature
"CB-ACCESS-TIMESTAMP" timestamp
"CB-ACCESS-PASSPHRASE" (:api-passphrase config)
"Content-Type" "application/json"}
:debug true}
opts))))
(defn get-accounts []
(send-signed-request "GET" "/accounts"))
(send-signed-request "GET" "/accounts")))
I've figured out the issue. Just in case anyone happens to have this very specific problem, I'm posting the solution. My error was that I was using the sha256-hmac
function from pandect, which returns a string hmac, then I was converting that to a byte array, base64 encoding it, and converting it back to a string. Somewhere in those conversions, or perhaps in the pandect function's conversion, the value is altered in an erroneous way.
What works is using the sha256-hmac*
function (note the asterisk) from pandect, which returns a raw byte array hmac, then base64 encoding that result directly, and converting it to a string. Below is the corrected, working code snippet, with which I was able to make a request to a private GDAX endpoint.
(defn create-signature
([timestamp method path]
(create-signature timestamp method path ""))
([timestamp method path body]
(let [secret-decoded (b64/decode (.getBytes (:api-secret config)))
prehash-string (str timestamp (clojure.string/upper-case method) path body)
hmac (sha256-hmac* prehash-string secret-decoded)]
(-> hmac
b64/encode
String.))))