For a full guide with more examples, see Verify Webhook Signatures.
How does the callback signature work?
CryptAPI signs every callback with a public-key scheme, using a 1024-bit RSA SHA256 signature. Because it signs the whole callback, you can trust that all the data came from our service.
If the callback is sent with GET, we sign the full URL (with all its GET parameters). If you asked to receive the callback with POST, we sign the whole request body.
You can fetch the public key that checks the signature from https://api.cryptapi.io/pubkey/.
We send the signature, base64-encoded, in the x-ca-signature header of the request.
How do I validate the callback?
The data you pass to the verification function must look like this:
uuid=TEST_aabf0e8e-cf58-4719-b5db-237c3e9a32c0&address_in=TEST_0x0000000000000000000000000000000000000000&address_out=TEST_0x0000000000000000000000000000000000000000&confirmations=1&txid_in=TEST_0x0000000000000000000000000000000000000000000000000000000000000000&txid_out=TEST_0x0000000000000000000000000000000000000000000000000000000000000000&fee=10000&fee_coin=0.01&value=1000000&value_coin=1&value_forwarded=990000&value_forwarded_coin=0.99&coin=test_coin&price=1&result=sent&pending=0Below are examples of how to verify the callback signature in several programming languages.
PHP
<?php
$pubkey = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3FT0Ym8b3myVxhQW7ESuuu6lo\ndGAsUJs4fq+Ey//jm27jQ7HHHDmP1YJO7XE7Jf/0DTEJgcw4EZhJFVwsk6d3+4fy\nBsn0tKeyGMiaE6cVkX0cy6Y85o8zgc/CwZKc0uw6d5siAo++xl2zl+RGMXCELQVE\nox7pp208zTvown577wIDAQAB\n-----END PUBLIC KEY-----";
$signature = base64_decode($_SERVER['HTTP_X_CA_SIGNATURE']);
$algo = OPENSSL_ALGO_SHA256;
// if request is GET
$data = "https://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
// if request is POST
$data = file_get_contents('php://input');
if (openssl_verify($data, $signature, $pubkey, $algo) == 1) {
// signature valid
} else {
// signature NOT valid
}Python (Django)
import base64
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
pub_str = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3FT0Ym8b3myVxhQW7ESuuu6lo\ndGAsUJs4fq+Ey//jm27jQ7HHHDmP1YJO7XE7Jf/0DTEJgcw4EZhJFVwsk6d3+4fy\nBsn0tKeyGMiaE6cVkX0cy6Y85o8zgc/CwZKc0uw6d5siAo++xl2zl+RGMXCELQVE\nox7pp208zTvown577wIDAQAB\n-----END PUBLIC KEY-----"
def verify_signature(request):
try:
if request.method == 'POST':
data = request.body
else:
data = request.build_absolute_uri().encode('utf8')
sig_b64 = request.headers['x-ca-signature']
# Load public key
public_key = serialization.load_pem_public_key(pub_str.encode('utf8'))
# Decode the signature from base64
signature = base64.b64decode(sig_b64)
# Verify the signature
public_key.verify(
signature,
data,
padding.PKCS1v15(),
hashes.SHA256()
)
return True
except Exception:
return FalsePython (Flask)
import base64
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from flask import jsonify, request
pub_str = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3FT0Ym8b3myVxhQW7ESuuu6lo\ndGAsUJs4fq+Ey//jm27jQ7HHHDmP1YJO7XE7Jf/0DTEJgcw4EZhJFVwsk6d3+4fy\nBsn0tKeyGMiaE6cVkX0cy6Y85o8zgc/CwZKc0uw6d5siAo++xl2zl+RGMXCELQVE\nox7pp208zTvown577wIDAQAB\n-----END PUBLIC KEY-----"
@app.route("/callback", methods=['GET', 'POST'])
def callback():
try:
if request.method == 'POST':
data = request.get_data(as_text=True)
else:
data = request.url
sig_b64 = request.headers.get('x-ca-signature')
if not sig_b64:
return jsonify({"error": "Missing signature header"}), 400
public_key = serialization.load_pem_public_key(pub_str.encode('utf8'))
signature = base64.b64decode(sig_b64)
public_key.verify(
signature,
data.encode('utf8'),
padding.PKCS1v15(),
hashes.SHA256()
)
return jsonify({"verified": True})
except Exception as e:
return jsonify({"verified": False, "error": str(e)}), 400Node.js
const crypto = require("crypto")
const pubkey = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3FT0Ym8b3myVxhQW7ESuuu6lo\ndGAsUJs4fq+Ey//jm27jQ7HHHDmP1YJO7XE7Jf/0DTEJgcw4EZhJFVwsk6d3+4fy\nBsn0tKeyGMiaE6cVkX0cy6Y85o8zgc/CwZKc0uw6d5siAo++xl2zl+RGMXCELQVE\nox7pp208zTvown577wIDAQAB\n-----END PUBLIC KEY-----"
const sig_b64 = req.headers['x-ca-signature']
const signature = Buffer.from(sig_b64, 'base64')
// if GET
const data = req.protocol + '://' + req.get('host') + req.originalUrl
verify(data, pubkey, signature)
// if POST
let rawBody = ''
req.on('data', function (chunk) {
rawBody += chunk
})
req.on('end', function () {
verify(rawBody, pubkey, signature)
})
function verify(data, pubkey, signature) {
const verifier = crypto.createVerify('RSA-SHA256')
verifier.update(data)
if (verifier.verify(pubkey, signature)) {
// signature is valid
} else {
// signature NOT valid
}
}