Search documentation

Find pages, endpoints, parameters, and guides.

How to verify the callback signature

View as Markdown

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:

text
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=0

Below are examples of how to verify the callback signature in several programming languages.

PHP

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)

Python
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 False

Python (Flask)

Python
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)}), 400

Node.js

JavaScript
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
    }
}