Appearance
Authentication
Authenticated endpoints require HMAC-based API key authentication. Public endpoints under /v1/public/* do not require authentication.
API Keys
API keys are generated from the VALR website under your account settings. Each key has scoped permissions that control what actions it can perform:
| Permission | Description |
|---|---|
| View access | Read account balances, order history, and market data |
| Trade | Place and cancel orders |
| Transfer | Transfer funds between wallets |
| Withdraw | Withdraw crypto and fiat |
| Link Bank Account | Link and manage bank accounts |
API keys and secrets are 64 characters long. Example format:
API Key: 4961b74efac86b25cce8fbe4c9811c4c7a787b7a5996660afcc2e287ad864363
API Secret: 4961b74efac86b25cce8fbe4c9811c4c7a787b7a5996660afcc2e287ad864363⚠️ WARNING
If your API Secret is compromised, your funds are at risk.
Security Best Practices
- Never send your API secret in requests — it is only used locally to generate signatures
- Do not share your API key or secret with anyone
- Do not commit keys to source control — use environment variables or a secrets manager
- Delete and regenerate keys immediately if you suspect they have been compromised
Request Signing
Every authenticated request must be signed using HMAC-SHA512. The signature is computed over a concatenation of the following parameters:
| Parameter | Description |
|---|---|
timestamp | Current Unix timestamp in milliseconds (must match the X-VALR-TIMESTAMP header) |
verb | HTTP method in uppercase (GET, POST, PUT, DELETE) |
path | Request path including query parameters (e.g., /v1/account/balances) |
body | JSON request body (empty string for requests without a body) |
subaccountId | Sub-account ID (empty string if not using sub-accounts) |
Signature = HMAC-SHA512(API_SECRET, timestamp + verb + path + body + subaccountId)JSON Key Ordering
The body parameter must be the exact JSON string sent in the request body. JSON key ordering matters — if your serialisation library reorders keys, the signature will not match.
Code Examples
JavaScript (Node.js)
javascript
const crypto = require('crypto');
function signRequest(apiSecret, timestamp, verb, path, body = '', subaccountId = '') {
return crypto
.createHmac('sha512', apiSecret)
.update(timestamp.toString())
.update(verb.toUpperCase())
.update(path)
.update(body)
.update(subaccountId)
.digest('hex');
}Go
go
package main
import (
"crypto/hmac"
"crypto/sha512"
"encoding/hex"
"fmt"
"strconv"
"strings"
"time"
)
func signRequest(apiSecret string, timestamp time.Time, verb string, path string, body string, subaccountId string) string {
mac := hmac.New(sha512.New, []byte(apiSecret))
timestampString := strconv.FormatInt(timestamp.UnixNano()/1000000, 10)
mac.Write([]byte(timestampString))
mac.Write([]byte(strings.ToUpper(verb)))
mac.Write([]byte(path))
mac.Write([]byte(body))
mac.Write([]byte(subaccountId))
return hex.EncodeToString(mac.Sum(nil))
}
func main() {
timestamp := time.Now()
signature := signRequest("your_api_secret", timestamp, "GET", "/v1/account/balances", "", "")
fmt.Println(signature)
}C#
csharp
using System;
using System.Security.Cryptography;
using System.Text;
public static class ValrAuth
{
public static string SignRequest(string apiSecret, long timestamp, string verb, string path, string body = "", string subaccountId = "")
{
var message = timestamp.ToString() + verb.ToUpper() + path + body + subaccountId;
using var hmac = new HMACSHA512(Encoding.UTF8.GetBytes(apiSecret));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
return BitConverter.ToString(hash).Replace("-", "").ToLower();
}
}Python
python
import hmac
import hashlib
import time
def sign_request(api_secret, timestamp, verb, path, body='', subaccount_id=''):
message = str(timestamp) + verb.upper() + path + body + subaccount_id
signature = hmac.new(
api_secret.encode(),
message.encode(),
hashlib.sha512
).hexdigest()
return signature
# Usage
timestamp = int(time.time() * 1000)
signature = sign_request('your_api_secret', timestamp, 'GET', '/v1/account/balances')Java
java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
public class ValrAuth {
public static String signRequest(String apiSecret, long timestamp, String verb, String path, String body, String subaccountId) {
try {
String message = Long.toString(timestamp) + verb.toUpperCase() + path + (body != null ? body : "") + (subaccountId != null ? subaccountId : "");
Mac mac = Mac.getInstance("HmacSHA512");
SecretKeySpec secretKey = new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA512");
mac.init(secretKey);
byte[] hash = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
} catch (Exception e) {
throw new RuntimeException("Failed to sign request", e);
}
}
}Sub-account Signing
When making requests on behalf of a sub-account, pass the sub-account ID as the subaccountId parameter in the signing functions above and include it in the X-VALR-SUB-ACCOUNT-ID header. Use an empty string when not operating on a sub-account.
Verify Your Implementation
Use the following test data to verify that your signing implementation produces the correct output.
GET Request Example
| Parameter | Value |
|---|---|
| API Secret | 4961b74efac86b25cce8fbe4c9811c4c7a787b7a5996660afcc2e287ad864363 |
| Timestamp | 1558014486185 |
| Verb | GET |
| Path | /v1/account/balances |
| Body | (empty) |
Expected signature:
9d52c181ed69460b49307b7891f04658e938b21181173844b5018b2fe783a6d4c62b8e67a03de4d099e7437ebfabe12c56233b73c6a0cc0f7ae87e05f6289928POST Request Example
| Parameter | Value |
|---|---|
| API Secret | 4961b74efac86b25cce8fbe4c9811c4c7a787b7a5996660afcc2e287ad864363 |
| Timestamp | 1558017528946 |
| Verb | POST |
| Path | /v1/orders/market |
| Body | {"customerOrderId":"ORDER-000001","pair":"BTCUSDC","side":"BUY","quoteAmount":"80000"} |
Expected signature:
09f536e3dfdad58443f16010a97a0a21ad27486b7b8d6d4103170d885410ed77f037f1fa628474190d4f5c08ca12c1acc850901f1c2e75c6d906ec3b32b008d0Making an Authenticated API Call
To make an authenticated request, include the following four headers:
| Header | Value |
|---|---|
X-VALR-API-KEY | Your API key |
X-VALR-SIGNATURE | The HMAC-SHA512 signature generated above |
X-VALR-TIMESTAMP | The timestamp used to generate the signature (milliseconds since epoch) |
X-VALR-SUB-ACCOUNT-ID | Your sub-account ID (omit if not using sub-accounts) |
Steps:
- Generate a Unix timestamp in milliseconds
- Build the signature string:
timestamp + verb + path + body + subaccountId - Compute the HMAC-SHA512 signature using your API secret
- Include all four headers in the request
Complete Request Examples
The following curl examples show fully assembled authenticated requests using the test vector data from the Verify Your Implementation section above.
GET request:
bash
curl -X GET "https://api.valr.com/v1/account/balances" \
-H "Content-Type: application/json" \
-H "X-VALR-API-KEY: your_api_key" \
-H "X-VALR-SIGNATURE: 9d52c181ed69460b49307b7891f04658e938b21181173844b5018b2fe783a6d4c62b8e67a03de4d099e7437ebfabe12c56233b73c6a0cc0f7ae87e05f6289928" \
-H "X-VALR-TIMESTAMP: 1558014486185"POST request:
bash
curl -X POST "https://api.valr.com/v1/orders/market" \
-H "Content-Type: application/json" \
-H "X-VALR-API-KEY: your_api_key" \
-H "X-VALR-SIGNATURE: 09f536e3dfdad58443f16010a97a0a21ad27486b7b8d6d4103170d885410ed77f037f1fa628474190d4f5c08ca12c1acc850901f1c2e75c6d906ec3b32b008d0" \
-H "X-VALR-TIMESTAMP: 1558017528946" \
-d '{"customerOrderId":"ORDER-000001","pair":"BTCUSDC","side":"BUY","quoteAmount":"80000"}'DELETE request with body:
Some endpoints (e.g., cancelling an order) use DELETE with a JSON body. The body must be included in the signature string, just as with POST or PUT.
bash
curl -X DELETE "https://api.valr.com/v2/orders/order" \
-H "Content-Type: application/json" \
-H "X-VALR-API-KEY: your_api_key" \
-H "X-VALR-SIGNATURE: your_generated_signature" \
-H "X-VALR-TIMESTAMP: 1558017528946" \
-d '{"orderId":"0c2a434b-1329-4f87-a66d-e9f12e7f1234","pair":"BTCUSDC"}'TIP
Replace your_api_key with your actual API key. The signatures shown here correspond to the test data in the verification section — in production, generate a fresh signature for every request.
WebSocket Authentication
WebSocket connections use the same three authentication headers (X-VALR-API-KEY, X-VALR-SIGNATURE, X-VALR-TIMESTAMP) sent with the first message on connection.
Public Endpoints
Endpoints under /v1/public/* do not require authentication. These include market data such as order books, trade history, and currency information.