Authenticating your API

Creating the correct Authentication headers to validate your requests to BVNK.

To interact with BVNK's APIs—including creating Embedded Partner Merchant Accounts and managing transactions on behalf of your EPMs—you'll need to utilize the API keys generated previously. If you haven't created your Hawk Auth ID and Hawk Auth Key yet, please refer to the earlier steps to generate these. Without them, you will be unable to proceed.

HAWK Authentication

BVNK employs Hawk Authentication to ensure the authenticity of requests made to our APIs. For a thorough understanding of this process, it's recommended to consult the guides available on the HAWK GitHub page. The HMAC is calculated using SHA256.

While HAWK supports optional payload validation for POST/PUT data, along with response payload validation, these features are not activated for the BVNK API and can be disregarded.

Code Examples

Below, you will find code examples for the most commonly used programming languages. These snippets are ready to use, but you will need to adjust the method, url, Hawk Auth ID, and Hawk Auth Key to reflect your specific requirements.

import org.apache.commons.codec.digest.HmacUtils;
import org.apache.http.client.utils.URIBuilder;

import java.net.URI;
import java.net.URISyntaxException;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Random;

/**
 * Main class demonstrates the generation of a Hawk authentication header.
 */
public class Main {
    // The credentials used to generate the Auth headers
    static String id = "<<Hawk Auth ID>>";
    static String key = "<<Hawk Auth Key>>";
    static String algorithm = "HmacSHA256"; // The hashing algorithm used.

    // The request to be performed
    static String method = "GET";
    static String url = "https://api.sandbox.bvnk.com/api/v1/merchant";

    public static void main(String[] args) throws Exception {
        String header = hawkHeader(url, method, id, key, algorithm);
        System.out.println(header);
    }

    /**
     * This method generates a timestamp of the current time in seconds
     *
     * @return A timestamp in seconds
     */
    public static long getTimestampInSeconds() {
        return System.currentTimeMillis() / 1000;
    }

    /**
     * This method creates a string of random alphanumeric characters
     *
     * @param length The required length of the string
     * @return A string of random characters
     */
    public static String generateNonce(int length) {
        String possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

        StringBuilder text = new StringBuilder(length);
        Random rnd = new SecureRandom();
        for (int i = 0; i < length; i++) {
            text.append(possible.charAt(rnd.nextInt(possible.length())));
        }
        return text.toString();
    }

    /**
     * This method generates a normalized string out of the header options
     *
     * @param type    The request type
     * @param ts      The timestamp in seconds
     * @param nonce   The generated nonce
     * @param method  The HTTP method of the call
     * @param url     The URL of the request
     * @param host    The host
     * @param port    The port
     * @param hash    The hash
     * @return A normalized string containing all parts to be hashed
     */
    public static String generateNormalizedString(String type, long ts, String nonce, String method, String url, String host, String port, String hash) {
        String headerVersion = "1";

        return "hawk." + headerVersion + "." +
                type + "\n" +
                ts + "\n" +
                nonce + "\n" +
                method.toUpperCase() + "\n" +
                url + "\n" +
                host.toLowerCase() + "\n" +
                port + "\n" +
                hash + "\n" +
                "\n";
    }

    /**
     * This method generates the request Message Authentication Code (MAC) using the MAC-SHA256 hashing algorithm
     *
     * @param type    The type to generate
     * @param key     The key used to generate the Auth header
     * @param options The options used to generate the code
     * @return a request MAC
     */
    public static String generateRequestMac(String type, String key, String options) {
        byte[] hmacSha256 = HmacUtils.hmacSha256(key, options);
        return Base64.getEncoder().encodeToString(hmacSha256);
    }

    /**
     * This method generates the Hawk authentication header
     *
     * @param uri  A full URI string
     * @param method The HTTP method (GET, POST, etc.)
     * @param id   The ID from the credentials
     * @param key  The key from the credentials
     * @param algorithm The algorithm from the credentials
     * @throws URISyntaxException Will throw an error if the provided URI string is of an invalid type.
     * @return The Hawk authorization header
     */
    public static String hawkHeader(String uri, String method, String id, String key, String algorithm) throws URISyntaxException {
        // Application time
        long timestamp = getTimestampInSeconds();

        // Generate the nonce
        String nonce = generateNonce(6);

        // Parse URI
        URI parsedUri = new URIBuilder(uri).build();
        String host = parsedUri.getHost();
        String port = String.valueOf(parsedUri.getPort() == -1 ? (parsedUri.getScheme().equals("http") ? 80 : 443) : parsedUri.getPort());
        String resource = parsedUri.getPath() + (parsedUri.getQuery() != null ? parsedUri.getQuery() : "");

        // Prepare artifacts object to be used in MAC generation
        String options = generateNormalizedString("header", timestamp, nonce, method, resource, host, port, "");

        // Generate the MAC
        String mac = generateRequestMac("header", key, options);

        // Construct Authorization header
        return "Hawk id=\"" + id + "\", ts=\"" + timestamp + "\", nonce=\"" + nonce + "\", mac=\"" + mac + "\"";
    }
}
using System;
using System.Security.Cryptography;
using System.Text;
using System.Linq;

public class MainClass
{
    // The credentials used to generate the Auth headers
    private static string id = "<<Hawk Auth ID>>";
    private static string key = "<<Hawk Auth Key>>";
    private static string algorithm = "HMACSHA256";

    // The request to be performed
    private static string method = "GET";
    private static string url = "https://api.sandbox.bvnk.com/api/v1/merchant";

    public static void Main(string[] args)
    {
        try
        {
            string header = HawkHeader(url, method, id, key, algorithm);
            Console.WriteLine(header);
        }
        catch (Exception e)
        {
            Console.Error.WriteLine("Error occurred:");
            Console.Error.WriteLine(e);
        }
    }

    /// <summary>
    /// Generates a timestamp of the current time in seconds.
    /// </summary>
    /// <returns>A timestamp in seconds.</returns>
    public static long GetTimestampInSeconds()
    {
        DateTimeOffset dto = DateTimeOffset.Now;
        return dto.ToUnixTimeSeconds();
    }

    /// <summary>
    /// Creates a string of random alphanumeric characters.
    /// </summary>
    /// <param name="length">The required length of the string.</param>
    /// <returns>A string of random characters.</returns>
    public static string GenerateNonce(int length)
    {
        const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        var random = new Random();
        return new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray());
    }

    /// <summary>
    /// Generates a normalized string out of the header options.
    /// </summary>
    /// <returns>A normalized string containing all parts to be hashed.</returns>
    public static string GenerateNormalizedString(string type, long ts, string nonce, string method, string url, string host, string port, string hash)
    {
        string headerVersion = "1";
        return $"hawk.{headerVersion}.{type}\n{ts}\n{nonce}\n{method.ToUpper()}\n{url}\n{host.ToLower()}\n{port}\n{hash}\n\n";
    }

    /// <summary>
    /// Generates the request Message Authentication Code (MAC).
    /// </summary>
    /// <returns>A request MAC generated from the HMAC-SHA256 hashing algorithm.</returns>
    public static string GenerateRequestMac(string type, string key, string options)
    {
        using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
        byte[] hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(options));
        return Convert.ToBase64String(hashBytes);
    }

    /// <summary>
    /// Generates the hawk authorization header.
    /// </summary>
    /// <returns>A Hawk authorization header including the Hawk Auth ID, timestamp, nonce, and MAC.</returns>
    public static string HawkHeader(string uriStr, string method, string id, string key, string algorithm)
    {
        long timestamp = GetTimestampInSeconds();
        string nonce = GenerateNonce(6);

        var uri = new UriBuilder(uriStr);
        string host = uri.Host;
        string port = uri.Port.ToString();
        string resource = uri.Path + (uri.Query.Length > 0 ? "?" +  uri.Query : "");

        string options = GenerateNormalizedString("header", timestamp, nonce, method, resource, host, port, "");
        string mac = GenerateRequestMac("header", key, options);

        return $"Hawk id=\"{id}\", ts=\"{timestamp}\", nonce=\"{nonce}\", mac=\"{mac}\"";
    }
}
const Hawk = require("hawk");

/**
 * The method to generate the hawk auth header
 * @param uri The full uri of the request
 * @param method The request method used
 * @param options The request options including credentials
 * @returns a Hawk authorization header including the Hawk Auth ID, time stamp, nonce, and MAC
 */
function hawkHeader(url, method, options) {
  const { header } = Hawk.client.header(url, method, options);
  return header;
}

// The credentials used to generate the Auth headers
const credentials = {
  id: "<<Hawk Auth ID>>",
  key: "<<Hawk Auth Key>>",
  algorithm: "sha256", // The hashing algorith used.
};

// The request to be performed
const request = {
  method: "GET",
  url: "/api/v1/merchant",
  host: "https://api.sandbox.bvnk.com",
};

console.log(
  hawkHeader(`${request.host}${request.url}`, request.method, {
    credentials,
  })
);

<?php


/**
 * This function generates a timestamp of the current time in seconds
 * 
 * @return int A timestamp in seconds
 */
function getTimestampInSeconds() {
    return time();
}

/**
 * This function creates a string of random alphanumeric characters
 * 
 * @param int $length The required length of the string
 * @return string A string of random characters
 */
function generateNonce($length) {
    $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    $randomString = '';
    for ($i = 0; $i < $length; $i++) {
        $randomString .= $characters[rand(0, strlen($characters) - 1)];
    }
    return $randomString;
}

/**
 * This function generates a normalized string out of the header options
 * 
 * @param string $type The request type
 * @param array $options The options array
 * @return string A normalized string containing all parts to be hashed
 */
function generateNormalizedString($type, $options) {
    $headerVersion = "1";

    $normalized = "hawk." .
        $headerVersion . "." .
        $type . "\n" .
        $options['ts'] . "\n" .
        $options['nonce'] . "\n" .
        strtoupper($options['method']) . "\n" .
        $options['resource'] . "\n" .
        strtolower($options['host']) . "\n" .
        $options['port'] . "\n" .
        ($options['hash'] ?? '') . "\n";

    $normalized .= "\n";

    return $normalized;
}

/**
 * This function generates the request Message Authentication Code (MAC) using the MAC-SHA256 hashing algorithm
 * 
 * @param string $type The type to generate
 * @param array $credentials The credentials used to generate the Auth header
 * @param array $options The options used to generate the code
 * @return string a request MAC
 */
function generateRequestMac($type, $credentials, $options) {
    $normalized = generateNormalizedString($type, $options);
    return base64_encode(hash_hmac($credentials['algorithm'], $normalized, $credentials['key'], true));
}

/**
 * This function generates the Hawk authentication header
 * 
 * @param string $uri A full URI string
 * @param string $method The HTTP method (GET, POST, etc.)
 * @param array $options Array containing credentials and other options
 * @throws Exception Will throw an exception if the provided arguments are of an invalid type.
 * @return string The Hawk authorization header
 */
function hawkHeader($uri, $method, $options) {
    // Validate inputs
    if (
        !$uri ||
        !is_string($uri) ||
        !$method ||
        !is_string($method) ||
        !$options ||
        !is_array($options)
    ) {
        throw new Exception("Invalid argument type");
    }

    // Application time
    $timestamp = getTimestampInSeconds();

    // Validate credentials
    $credentials = $options['credentials'];
    if (
        !$credentials ||
        !$credentials['id'] ||
        !$credentials['key'] ||
        !$credentials['algorithm']
    ) {
        throw new Exception("Invalid credentials");
    }

    // Calculate signature to hash
    $artifacts = [
        'ts' => $timestamp,
        'nonce' => generateNonce(6),
        'method' => $method,
        'resource' => parse_url($uri, PHP_URL_PATH) . (parse_url($uri, PHP_URL_QUERY) ? '?' . parse_url($uri, PHP_URL_QUERY) : ''),
        'host' => parse_url($uri, PHP_URL_HOST),
        'port' => parse_url($uri, PHP_URL_PORT) ?? (parse_url($uri, PHP_URL_SCHEME) === "http" ? 80 : 443),
    ];

    // Generate the MAC
    $mac = generateRequestMac("header", $credentials, $artifacts);

    // Construct Authorization header
    $header = 'Hawk id="' . $credentials['id'] . '", ts="' . $artifacts['ts'] . '", nonce="' . $artifacts['nonce'] . '", mac="' . $mac . '"';

    return $header;
}

// The credentials used to generate the Auth headers
$credentials = [
    'id' => "<<Hawk Auth ID>>",
    'key' => "<<Hawk Auth Key>>",
    'algorithm' => "sha256", // The hashing algorithm used.
];

// The request to be performed
$request = [
    'method' => "GET",
    'url' => "/api/v1/merchant",
    'host' => "https://api.sandbox.bvnk.com",
];

// Print Hawk Auth header
echo hawkHeader($request['host'] . $request['url'], $request['method'], ['credentials' => $credentials]);


?>
require 'openssl'
require 'base64'
require 'uri'

# Generates a timestamp of the current time in seconds
# @return [Integer] A timestamp in seconds
def get_timestamp_in_seconds
  Time.now.to_i
end

# Creates a string of random alphanumeric characters
# @param length [Integer] The required length of the string
# @return [String] A string of random characters
def generate_nonce(length)
  possible = ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a
  (0...length).map { possible[rand(possible.length)] }.join
end

# Generates a normalized string out of the header options
# @param type [String] The request type
# @param options [Hash] The options hash
# @return [String] A normalized string containing all parts to be hashed
def generate_normalized_string(type, options)
  header_version = "1"
  normalized = "hawk." +
  header_version +
  "." +
  type +
  "\n" +
  options[:ts].to_s +
  "\n" +
  options[:nonce] +
  "\n" +
  (options[:method] || "").upcase +
  "\n" +
  (options[:resource] || "") +
  "\n" +
  options[:host].downcase +
  "\n" +
  options[:port].to_s +
  "\n" +
  (options[:hash] || "") +
  "\n"

  normalized += "\n"
  normalized
end

# Generates the request Message Authentication Code (MAC) using the MAC-SHA256 hashing algorithm
# @param type [String] The type to generate
# @param credentials [Hash] The credentials used to generate the Auth header
# @param options [Hash] The options used to generate the code
# @return [String] a request MAC
def generate_request_mac(type, credentials, options)
  normalized = generate_normalized_string(type, options)
  hmac = OpenSSL::HMAC.new(credentials[:key], credentials[:algorithm])
  hmac.update(normalized)
  Base64.strict_encode64(hmac.digest)
end

# Generates the Hawk authentication header
# @param uri [String] A full URI string
# @param method [String] The HTTP method (GET, POST, etc.)
# @param options [Hash] Hash containing credentials and other options
# @raise [RuntimeError] If the provided arguments are of an invalid type.
# @return [String] The Hawk authorization header
def hawk_header(uri, method, options)
  uri = URI(uri) if uri.is_a? String
  timestamp = get_timestamp_in_seconds

  credentials = options[:credentials]
  if credentials.nil? || credentials[:id].nil? || credentials[:key].nil? || credentials[:algorithm].nil?
    raise "Invalid credentials"
  end

  artifacts = {
    ts: timestamp,
    nonce: generate_nonce(6),
    method: method,
    resource: uri.path + (uri.query.nil? ? '' : "?#{uri.query}"),
    host: uri.host,
    port: uri.port || (uri.scheme == "http" ? 80 : 443)
  }

  mac = generate_request_mac("header", credentials, artifacts)
  header = "Hawk id=\"#{credentials[:id]}\", ts=\"#{artifacts[:ts]}\", nonce=\"#{artifacts[:nonce]}\", mac=\"#{mac}\""

  header
end

credentials = {
  id: "<<Hawk Auth ID>>",
  key: "<<Hawk Auth Key>>",
  algorithm: "sha256",
}

request = {
  method: "GET",
  url: "/api/v1/merchant",
  host: "https://api.sandbox.bvnk.com",
}

header = hawk_header("#{request[:host]}#{request[:url]}", request[:method], {
  credentials: credentials
})

puts header
import time
import random
import string
import hmac
import hashlib
import urllib.parse
import base64

# This function generates a timestamp of the current time in seconds
def get_timestamp_in_seconds():
    return int(time.time())

# This function creates a string of random alphanumeric characters
def generate_nonce(length):
    return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(length))

# This function generates a normalized string out of the header options
def generate_normalized_string(type, options):
    header_version = "1"
    normalized = f'hawk.{header_version}.{type}\n{options["ts"]}\n{options["nonce"]}\n{options["method"].upper()}\n{options["resource"]}\n{options["host"].lower()}\n{options["port"]}\n{options["hash"]}\n\n'
    return normalized

# This method generates the request Message Authentication Code (MAC) using the MAC-SHA256 hashing algorithm
def generate_request_mac(type, credentials, options):
    normalized = generate_normalized_string(type, options)
    hmac_obj = hmac.new(credentials['key'].encode(), normalized.encode(), hashlib.sha256)
    return base64.b64encode(hmac_obj.digest()).decode()

# This method generates the Hawk authentication header
def hawk_header(uri, method, options):
    # Validate inputs
    if not uri or not isinstance(uri, str) or not method or not isinstance(method, str) or not options or not isinstance(options, dict):
        raise ValueError("Invalid argument type")

    # Application time
    timestamp = get_timestamp_in_seconds()

    # Validate credentials
    credentials = options['credentials']
    if not credentials or not credentials['id'] or not credentials['key'] or not credentials['algorithm']:
        raise ValueError("Invalid credentials")

    # Parse URI if it is a string
    parsed_uri = urllib.parse.urlparse(uri)

    # Prepare artifacts object to be used in MAC generation
    artifacts = {
        'ts': timestamp,
        'nonce': generate_nonce(6),
        'method': method,
        'resource': parsed_uri.path + parsed_uri.query, # Maintains trailing '?'
        'host': parsed_uri.hostname,
        'port': parsed_uri.port if parsed_uri.port else (80 if parsed_uri.scheme == 'http' else 443),
        'hash': ''
    }

    # Generate the MAC
    mac = generate_request_mac('header', credentials, artifacts)

    # Construct Authorization header
    header = f'Hawk id="{credentials["id"]}", ts="{artifacts["ts"]}", nonce="{artifacts["nonce"]}", mac="{mac}"'

    return header

# The credentials used to generate the Auth headers
credentials = {
  'id': "<<Hawk Auth ID>>",
  'key': "<<Hawk Auth Key>>",
  'algorithm': "sha256", # The hashing algorithm used.
}

# The request to be performed
request = {
  'method': "GET",
  'url': "/api/v1/merchant",
  'host': "https://api.sandbox.bvnk.com",
}

print(hawk_header(f'{request["host"]}{request["url"]}', request["method"], {'credentials': credentials}))

🚧

The HTTP method and endpoint URL significantly influence the generation of the correct hash values in your authorization header. It's crucial to ensure these values are dynamically updated to align with your actual requests; otherwise, your requests may fail.