Authentication
All requests must contain the following headers:
Header | Description |
---|---|
X-Api-Key | Your API key (SHA256 from public key). |
X-Api-Signature | The query's serialized body signed by your private key according to the RSA-SHA256 method. |
Authenticate and send signed requests
The following code samples illustrate how to authenticate and send signed requests.
- Node.js
- Python
- PHP
- Go
note
Node.js >= 15 is required.
const crypto = require("crypto");
const privateKeyString = "<<Your private key>>";
const privateKey = crypto.createPrivateKey({
key: privateKeyString,
format: 'der',
type: 'pkcs8',
encoding: 'hex'
});
const publicKey = crypto.createPublicKey(privateKey).export({
type: 'pkcs1',
format: 'der'
});
const message = {
"jsonrpc": "2.0",
"id": "test",
"method": "getStatus",
"params": {
"id": "psj42e728a572mtkz"
}
};
const signature = crypto.sign('sha256', Buffer.from(JSON.stringify(message)), {
key: privateKey,
type: 'pkcs8',
format: 'der'
});
// ----------------------------------
const request = require('request');
const options = {
'method': 'POST',
'url': 'https://api.changelly.com/v2',
'headers': {
'Content-Type': 'application/json',
'X-Api-Key': crypto.createHash('sha256').update(publicKey).digest('base64'),
'X-Api-Signature': signature.toString('base64')
},
body: JSON.stringify(message)
};
request(options, function (error, response) {
if (error) throw new Error(error);
console.log(response.body);
});
import base64
import binascii
import json
import logging
from typing import List
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15
from requests import post, Response
logging.basicConfig(level=logging.INFO)
class ApiException(Exception):
def __init__(self, code: int, message: str):
self.code = code
self.message = message
class ApiService:
def __init__(self, url: str, private_key: str, x_api_key: str):
self.url = url
self.private_key = private_key
self.x_api_key = x_api_key
def _request(self, method: str, params: dict or list = None) -> Response or List[dict]:
params = params if params else {}
message = {
'jsonrpc': '2.0',
'id': 'test',
'method': method,
'params': params
}
response = post(self.url, headers=self._get_headers(body=message), json=message)
if response.ok:
response_body = response.json()
logging.info(f'{method} response: {response_body} (request: {params})')
if response_body.get('error'):
error = response_body['error']
raise ApiException(error['code'], error['message'])
return response_body['result']
raise ApiException(response.status_code, response.text)
def _sign_request(self, body: dict) -> bytes:
decoded_private_key = binascii.unhexlify(self.private_key)
private_key = RSA.import_key(decoded_private_key)
message = json.dumps(body).encode('utf-8')
h = SHA256.new(message)
signature = pkcs1_15.new(private_key).sign(h)
return base64.b64encode(signature)
def _get_headers(self, body: dict) -> dict:
signature = self._sign_request(body)
return {
'content-type': 'application/json',
'X-Api-Key': self.x_api_key,
'X-Api-Signature': signature,
}
def get_pairs_params(self, currency_from: str, currency_to: str):
return self._request('getPairsParams', params=[{'from': currency_from, 'to': currency_to}])
api = ApiService(
url='https://api.changelly.com/v2/',
private_key='<<Your private key>>',
x_api_key='<<SHA256 hash of your public key in Base64 format>>',
)
api.get_pairs_params('eth', 'btc')
note
PHP 8 is required.
<?php
require __DIR__ . "/vendor/autoload.php";
use phpseclib3\Crypt\RSA;
class ApiException extends Exception { }
class ApiService {
private $url;
private $privateKey;
private $apiKey;
public function __construct($url, $privateKeyData, $apiKey) {
$this->url = $url;
$this->apiKey = $apiKey;
$privateKeyData = hex2bin($privateKeyData);
$this->privateKey = RSA::load($privateKeyData);
}
private function request($method, $params = null) {
$params = $params ?? [];
$message = [
'jsonrpc' => '2.0',
'id' => 'test',
'method' => $method,
'params' => $params
];
$response = $this->post($this->url, [
'headers' => $this->getHeaders($message),
'json' => $message
]);
if ($response['httpCode'] == 200) {
if (isset($response['body']['error'])) {
$error = $response['body']['error'];
throw new ApiException($error['message'], $error['code']);
}
return $response['body']['result'];
}
throw new ApiException($response['body'] ?? '', $response['httpCode']);
}
private function signRequest($body) {
$message = json_encode($body);
openssl_sign($message, $signature, $this->privateKey, OPENSSL_ALGO_SHA256);
return base64_encode($signature);
}
private function getHeaders($body) {
$signature = $this->signRequest($body);
return [
'content-type: application/json',
'X-Api-Key: ' . $this->apiKey,
'X-Api-Signature: ' . $signature
];
}
private function post($url, $data) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($data['json']),
CURLOPT_HTTPHEADER => $data['headers']
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'body' => json_decode($response, true),
'httpCode' => $httpCode
];
}
public function getPairsParams($currencyFrom, $currencyTo) {
return $this->request('getPairsParams', [['from' => $currencyFrom, 'to' => $currencyTo]]);
}
public function getTransactions() {
return $this->request('getTransactions');
}
}
$privateKey = '<<Your private key>>';
$apiKey = '<<SHA256 hash of your public key in Base64 format>>';
$api = new ApiService(
'https://api.changelly.com/v2/',
$privateKey,
$apiKey
);
try {
$limits = $api->getPairsParams('eth', 'btc');
var_dump($limits);
$transactions = $api->getTransactions();
var_dump($transactions);
} catch (ApiException $error) {
echo "Error with code " . $error->getCode() . PHP_EOL;
if ($error->getMessage()) {
echo "Message: " . $error->getMessage() . PHP_EOL;
}
} catch (Exception $error) {
echo 'Error: ' . $error->getMessage() . PHP_EOL;
}
note
This code sample was created using Go 1.21.1.
package main
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
// Your generated private key as a string
privateKeyString := "<<YOUR_PRIVATE_KEY>>"
// Decode the hex-encoded private key
decodedPrivateKey, err := hex.DecodeString(privateKeyString)
if err != nil {
panic(err)
}
// Parse the PKCS8 private key
privateKey, err := x509.ParsePKCS8PrivateKey(decodedPrivateKey)
if err != nil {
panic(err)
}
rsaPrivateKey, ok := privateKey.(*rsa.PrivateKey)
if !ok {
panic("Failed to cast to RSA Private Key")
}
// Marshal the public key to DER format
publicDER := x509.MarshalPKCS1PublicKey(&rsaPrivateKey.PublicKey)
// Define the message payload
message := map[string]interface{}{
"jsonrpc": "2.0",
"id": "test",
"method": "getStatus",
"params": map[string]string{
"id": "psj42e728a572mtkz",
},
}
messageBytes, err := json.Marshal(message)
if err != nil {
panic(err)
}
// Sign the message with the RSA private key
hashed := sha256.Sum256(messageBytes)
signature, err := rsa.SignPKCS1v15(rand.Reader, rsaPrivateKey, crypto.SHA256, hashed[:])
if err != nil {
panic(err)
}
// Prepare the HTTP POST request
client := &http.Client{}
req, err := http.NewRequest("POST", "https://api.changelly.com/v2", bytes.NewReader(messageBytes))
if err != nil {
panic(err)
}
// Set request headers
hash := sha256.Sum256(publicDER)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Api-Key", base64.StdEncoding.EncodeToString(hash[:]))
req.Header.Set("X-Api-Signature", base64.StdEncoding.EncodeToString(signature))
// Send the request
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// Check if the status code is 401
if resp.StatusCode == http.StatusUnauthorized {
fmt.Println("Received status code 401 Unauthorized")
}
// Read and display the response body
respBody, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(respBody))
}