How I captured OpenNode Webhook (PHP)

How I captured OpenNode Webhook (PHP)

OpenNode makes it easy for developers to accept Bitcoin payments. However, I couldn't find any PHP code samples on how to capture each payment information. In this guide, you will see how I was able to securely capture each charge via webhook.

This post assumes you already have a OpenNode account and you can successfully create a bitcoin charge.

creating a charge with callback

I created a charge using the official PHP SDK. And to receive a webhook notification, I provided a callback_url:

$charge_params = array(
   "amount"        => 50.00,
   "currency"      => "USD",
   "callback_url"  => "https://webhook.site/b07fsfwrw"
);

By providing a callback_url, OpenNode sends a POST request to my callback_url. Each request sent contains a payload with the following information:

POST callback_url | application/x-www-form-urlencoded
{
    id: id,
    callback_url: callback_url,
    success_url: success_url,
    status: status,
    order_id: order_id,
    description: description,
    price: price,
    fee: fee,
    auto_settle: auto_settle,
    hashed_order: hashed_order
}

capturing the event payload

To capture this payload, I created the callback route on my application with the following code:


if ( $json = json_decode( file_get_contents("php://input"), true ) ) {
    $charge = $json;
} else {
    $charge = $_POST;
}

if ( empty($charge) || !is_array($charge) ) {
    exit("charge error");
}

$apiKey      = "OPENNODE-TOKEN";
$recieved    = $charge["hashed_order"];
$calculated  = hash_hmac( "sha256", $charge["id"], $apiKey );

if ( !hash_equals( $calculated, $recieved) ) {
    exit("failed");
}

print_r( $charge ); // print the charge data

This successfully captures the webhook for each charge on OpenNode.

json or form-data

You may notice on the first line I try to capture the charge as a JSON payload but OpenNode always sends webhook data as form-urlencoded. Don't mind, it is just my "bullet-proof" way of dealing with any webhook and the majority of webhook data is in JSON.

However, if you mind, rather than this:

if ( $json = json_decode( file_get_contents("php://input"), true ) ) {
    $charge = $json;
} else {
    $charge = $_POST;
}

you remove the if statements:

$charge = $_POST;

works 100% with OpenNode.

block unknown request

To avoid touching stories, you have to validate the webhook requests.

OpenNode signs all charge related events it sends to your callback URL with a hashed_order field on the event payload. This allows you to validate that the events were sent by OpenNode and not by a third party.

To validate, you verify the signatures by computing an HMAC with the SHA256 hash function. This is very easy in PHP. Just use the hash_hmac function:

$apiKey      = "YOUR OPENNODE API KEY";
$recieved    = $charge["hashed_order"];
$calculated  = hash_hmac("sha256", $charge["id"], $apiKey);

The hash_hmac function requires three parameters; the hash algorithm, the data or message, and the key.

  • the hash algorithm is sha256
  • the hash data or message is the hashed_order field in the webhook payload
  • the key is your OpenNode api key.

We then check if the calculated hash matches the hashed_order in the event payload.

if ( !hash_equals( $calculated, $recieved) ) {
    exit("failed");
}

This way we ensure no third party request will pass through successfully.

You can then proceed with whatever you intend to use the event payload for knowing it coming from OpenNode. You can read the official webhook documentation.

I hope this help.