Skip to content

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:

PermissionDescription
View accessRead account balances, order history, and market data
TradePlace and cancel orders
TransferTransfer funds between wallets
WithdrawWithdraw crypto and fiat
Link Bank AccountLink 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:

ParameterDescription
timestampCurrent Unix timestamp in milliseconds (must match the X-VALR-TIMESTAMP header)
verbHTTP method in uppercase (GET, POST, PUT, DELETE)
pathRequest path including query parameters (e.g., /v1/account/balances)
bodyJSON request body (empty string for requests without a body)
subaccountIdSub-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

ParameterValue
API Secret4961b74efac86b25cce8fbe4c9811c4c7a787b7a5996660afcc2e287ad864363
Timestamp1558014486185
VerbGET
Path/v1/account/balances
Body(empty)

Expected signature:

9d52c181ed69460b49307b7891f04658e938b21181173844b5018b2fe783a6d4c62b8e67a03de4d099e7437ebfabe12c56233b73c6a0cc0f7ae87e05f6289928

POST Request Example

ParameterValue
API Secret4961b74efac86b25cce8fbe4c9811c4c7a787b7a5996660afcc2e287ad864363
Timestamp1558017528946
VerbPOST
Path/v1/orders/market
Body{"customerOrderId":"ORDER-000001","pair":"BTCUSDC","side":"BUY","quoteAmount":"80000"}

Expected signature:

09f536e3dfdad58443f16010a97a0a21ad27486b7b8d6d4103170d885410ed77f037f1fa628474190d4f5c08ca12c1acc850901f1c2e75c6d906ec3b32b008d0

Making an Authenticated API Call

To make an authenticated request, include the following four headers:

HeaderValue
X-VALR-API-KEYYour API key
X-VALR-SIGNATUREThe HMAC-SHA512 signature generated above
X-VALR-TIMESTAMPThe timestamp used to generate the signature (milliseconds since epoch)
X-VALR-SUB-ACCOUNT-IDYour sub-account ID (omit if not using sub-accounts)

Steps:

  1. Generate a Unix timestamp in milliseconds
  2. Build the signature string: timestamp + verb + path + body + subaccountId
  3. Compute the HMAC-SHA512 signature using your API secret
  4. 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.