How to Verify the Callback/IPN Signature

Modified on Fri, 6 Dec at 5:03 AM

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 False


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


Node.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

Let us know how can we improve this article!

Select at least one of the reasons
CAPTCHA verification is required.

Feedback sent

We appreciate your effort and will try to fix the article