How does the callback signature work?
BlockBee's callback signature uses a 1024-bit RSA SHA256 signature with a public-key signature scheme to sign the entire callback sent to your service, ensuring that all data was sent from our service and can be trusted.
If the request is sent via GET, the full URL with all GET parameters is signed. If the callback is requested via POST, the entire request body is signed.
The signature is sent via the x-ca-signature header of the request, and is base64-encoded. The public key used to validate the signature can be fetched from the following endpoint: https://api.blockbee.io/pubkey/.
How do I validate the callback?
Here is an example of how the data provided to the verification function must look like.
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 achieving this using various 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)
First install pyOpenSSL.
import base64
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
pub_str = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3FT0Ym8b3myVxhQW7ESuuu6lo\ndGAsUJs4fq+Ey//jm27jQ7HHHDmP1YJO7XE7Jf/0DTEJgcw4EZhJFVwsk6d3+4fy\nBsn0tKeyGMiaE6cVkX0cy6Y85o8zgc/CwZKc0uw6d5siAo++xl2zl+RGMXCELQVE\nox7pp208zTvown577wIDAQAB\n-----END PUBLIC KEY-----";
try:
if request.method == 'POST':
data = request.body
else:
data = request.build_absolute_uri()
sig_b64 = request.headers['x-ca-signature']
# Load public key
public_key = serialization.load_pem_public_key(
pub_str.encode('utf8'),
backend=default_backend()
)
# Decode the signature from base64
signature = base64.b64decode(sig_b64)
# Verify the signature
public_key.verify(
signature,
data.encode('utf8'),
padding.PKCS1v15(),
hashes.SHA256()
)
return True
except Exception:
return FalsePython (Flask)
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'),
backend=default_backend()
)
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
except Exception:
return FalseNode.js
let crypto = require("crypto")
let pubkey = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3FT0Ym8b3myVxhQW7ESuuu6lo\ndGAsUJs4fq+Ey//jm27jQ7HHHDmP1YJO7XE7Jf/0DTEJgcw4EZhJFVwsk6d3+4fy\nBsn0tKeyGMiaE6cVkX0cy6Y85o8zgc/CwZKc0uw6d5siAo++xl2zl+RGMXCELQVE\nox7pp208zTvown577wIDAQAB\n-----END PUBLIC KEY-----"
let sig_b64 = req.headers['x-ca-signature']
let signature = new Buffer(sig_b64, 'base64');
// if GET
let data = req.protocol + '://' + req.get('host') + req.originalUrl;
// if POST
rawBody = ''
req.on('data', function(chunk) {
rawBody += chunk
});
req.on('end', function(){
let data = rawBody
verify(data, pubkey, signature)
});
function verify(data, pubkey, signature) {
let verifier = crypto.createVerify('RSA-SHA256');
verifier.update (data);
if (verifier.verify (pubkey, signature)) {
// signature is valid
} else {
// signature NOT valid
}
}Was this article helpful?
That’s Great!
Thank you for your feedback
Sorry! We couldn't be helpful
Thank you for your feedback
Feedback sent
We appreciate your effort and will try to fix the article