Securing Callbacks through Hashing

These settings are related to the technical integration of BitLabs.

1445

Hashing is really important to make the callbacks safe! Please do not use S2S callbacks without validating the hash.

In order to make callbacks safe, we append a "&hash=[HASH] at the end of each callback. This is an important feature for you to ensure that the callback, which you use as a base to reward your users, comes from BitLabs and is not fake. This hash consists of a HEX-encoded SHA1 HMAC. The whole URL is hashed with the secret key of the App. By checking the hash, you can be sure that we actually sent the postback and that it is legit.

Example

Inputs:
Callback URL: "https://publisher.com/complete?uid=8cc877ee-af19-488d-b28d-216fb866b996&val=500"

App Secret: "JLOIAUNMHFli7ZJOQVEzm98rzqnm9"

Hash: "dbcd6bb8ca677344592842a52b4fca9bec36cd4b"

Output (Final Callback Sent to the Publisher)
https://publisher.com/complete?uid=8cc877ee-af19-488d-b28d-216fb866b996&val=500&hash=dbcd6bb8ca677344592842a52b4fca9bec36cd4b

Hashing Code Examples

These code samples are meant to show you how to validate hashes. It can also be used as an example of handling callbacks in general.

🚧

Do not encode/decode the URL before generating the hash. This will cause differences in the result. Take the URL as it is, without manipulating.

const crypto = require('crypto');

function checkHash(url, secretKey) {
  const splitUrl = url.split('&hash=');
  const hmac = crypto.createHmac('sha1', secretKey);
  hmac.write(splitUrl[0]);
  hmac.end();
  return splitUrl[1] === hmac.read().toString('hex');
}

const secretKey = '';
const url = 'http://url.com?param1=foo&param2=bar&hash=3171f6b78e06cadcec4c9c3b15f858b8400e8738';
console.log(checkHash(url, secretKey));
<?php
// This has to be your App's secret key that you can find on the Dashboard
$secret_key = "";
// Get the currently active http protocol
$protocol = isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] === "on" ? "https" : "http";
// Build the full callback URL
// Example: https://url.com?param1=foo&param2=bar&hash=3171f6b78e06cadcec4c9c3b15f858b8400e8738
$url = "$protocol://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
// Save all query parameters of the callback into the $params array
$url_components = parse_url($url);
parse_str($url_components["query"], $params);
// Get the callback URL without the "hash" query parameter
// Example: https://url.com?param1=foo&param2=bar
$url_val = substr($url, 0, -strlen("&hash=$params[hash]"));
// Generate a hash from the complete callback URL without the "hash" query parameter
$hash = hash_hmac("sha1", $url_val, $secret_key);

//Check if the generated hash is the same as the "hash" query parameter
if ($params["hash"] === $hash) {
  echo "valid";
} else {
  echo "invalid";
}
using System;
using System.Web;
using System.Text;
using System.Security.Cryptography;
using System.Collections.Specialized;

public class Program {
  public static void Main() {
    string secretKey = "";
    string url = "http://url.com?param1=foo&param2=bar&hash=3171f6b78e06cadcec4c9c3b15f858b8400e8738";
    if (IsURLHashValid(url, secretKey)) {
      Console.WriteLine("valid");
    } else {
      Console.WriteLine("invalid");
    }
  }

	private static bool IsURLHashValid(string url, string secretKey)
	{
		Uri uri = new(url);
		NameValueCollection queryString = HttpUtility.ParseQueryString(uri.Query);
		var urlHash = queryString.Get("hash");
		if (urlHash is null) return false;
		queryString.Remove("hash");
		string baseURL = uri.GetLeftPart(UriPartial.Path);
		string urlValue = queryString.Count > 0 ? string.Format("{0}?{1}", baseURL, queryString) : baseURL;
		return urlHash == CreateHash(urlValue, secretKey);
	}
  
	public static string CreateHash(string urlValue, string secretKey)
	{
		byte[] buffer = Encoding.UTF8.GetBytes(urlValue);
		var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(secretKey));
		return BitConverter.ToString(hmac.ComputeHash(buffer)).Replace("-", "").ToLower();
	}
}