Receive transaction history report via webhook
Remark!
If you are interested in this functionality please contact your account manager.
Generating account level webhook and secret
Contact your account manager. Provide them with your webhook url and he/she will setup your account level webhook and will provide a secret key for you.
Generating transactions report
To generate report, use the Transactions report endpoint with &delivery=webhook
added to the path.
curl 'https://api.staging.bvnk.com/api/transaction/report?&format=csv&fromDate=2024-03-27&toDate=2024-04-26&delivery=webhook' \
-H 'authorization: Hawk id="gwfA3cAerOLoQstDlR2bhMtiDmgxddjolPIx8IPlMKdcsKEUFLA8cGTfnqr78dOR", ts="1714110818", nonce="sptyTuHVFI", mac="CugWKQMl1wg0FPHrroOIe1tLUroWiuZzb3R1B/naSLU="' \
The example above will trigger asynchronous transaction report generation for all of your wallets. Once the report is ready it will be send it via webhook.
Other possible parameters are:
Parameter | Type | Required | Description |
---|---|---|---|
fromDate | string | Yes | Export range from date in format 'YYYY-MM-dd'. |
toDate | string | Yes | Export range to date in format 'YYYY-MM-dd'. |
delivery | string | No | How the report will be received. Could be one of webhook, email. Defaults to email. In this scenario, please provide delivery=webhook |
format | string | No | The format of the generated file report. Could be one of csv, json. Defaults to json |
walletId | integer | No | Filter the transactions based on walletId. If omitted all transactions will be included |
Handling transaction report created
Webhooks for transaction reports will be sent as an HTTP POST with Content-Type: application/json
containing the following payload:
{
"event": "reportCreated",
"data": {
"url": "https://coindirect-filestore.s3.amazonaws.com/com.bvnk.reports.BackgroundReport/b9ee2544-b838-4c37-9051-3974c4d19fef.csv?X-Amz-Security-Token=IQoJb3JpZ2luX2VjEGAaCWV1LXdlc3QtMSJHMEUCIQChnP0FdjPn0tJ8HMz%2BsKwRgHwvqKO8shW1Fs1R57FfuwIgYHtQLOrJoY2WiKHO3SmAAcVHUqptlzakFz6XM9tkkSMqkwUIqf%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARAFGgwzODU0ODUwMzkxMTEiDAxHTlmQuRKIa3%2FGMSrnBDRX%2BpF1M9F%2FKDbtH3mxQvG1nORC%2BTcxgrNnTFmYcjpr%2BLFpa22PnVrbPXFEaMnsCzySRqgi%2FBz4QNUAZdGvvDZswQFh53a2pC5gmQN3xWd1pzfzkeOaZwVgbyaq1dsTteLQhZdI9lzJG7ceXaLyHWw23P%2B3JocLJKqRp0ugdc3Ksj3RhtgerqxRx80mToq98xB7ndjhoWyVTALQ4Bk7dpBEzoF8ZpQ8zC46uJufwmazn8tZDJK%2Fg603djWfMfCR53lpenLtIxxWD0Feph2IoI5arn2XF%2BZqCg2j8kWAriMYyfOohG1SLcHzjozscsCWNlZgsFJBCypiZltnnUGbPLhUjtodW6a8F7Oib6yB5wEIGcVcvUj1obq%2FL7%2FN78wHaRH5PlzcD%2F9Z%2BoqBPwu%2BKSgl8QTNp%2FG8cO8KwXvm4Gq6%2Fix%2FZMKsfLjPWQ%2BCeZTfm8t9WBFPd5Var89rfZIr26pz5R8XAtIMmVqY8mbLZeDKVem5DwVt1fwBlDb%2BKC9cdduWLfuAFh6kRTG744d71XCetYTw8TUMdZ7emcVHk%2BjHC8qHqwE1bYsG0DAoXJ%2BARW6xwqAxID%2FjkmhJ8Nn%2B07UBtHU6UPhHhCpBLYCu%2B75%2BLwkoqOUWQsm9CJ7ePpDaMT%2B9ovmnolv%2BuRoIhfcKhx3aZSLm2uhrF%2FL4AcdXS%2BNitALmNL5c8F6RX6esQzpuksmLHxq8aFTfz1vEgbklG%2BTES9bsYF8B8vV%2FSNz6VyRohQGGxgEKTgj2%2Bxmr35HpeO86jbz6EU3qHtCF662TCRBlXgnqkQqjzyNVzYrKAu86NFrwIinemjCs9amxBjqbAaIBk%2BWvSyb%2FTVqBq%2FPJSgDaMlmDsWvFMDgY4XcSHx1XuIpTrZxHDErObDG2oHG2VacFUtQw2aeK8OEig94pWd%2B9QhtBiMjbu0LX4IJhX7vNFZ%2BMKC4U7wQJ0lu3SbYwHFNQWWkx07ajTN24DruGQkrbgG%2BzG6u1WoXnr35cUrR0fJUnhqcb0ILVBA7gDpgoW1Xyiz7Bg0mnIKjb&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20240425T155243Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86394&X-Amz-Credential=ASIAVTQFS4YDTMPXPNXR%2F20240425%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=872cc212faa8676dcb492f195ddfd9de054e7cf2024165f0d795f9c5b56e9600"
}
}
You can check that the event name is correct and then download the report. Keep in mind that the link to the report will be active for the next 24 hours. After that the report won't be accessible.
Acknowledging events
BVNK is expecting a 200
status code upon receipt of a webhook that has been successfully sent.
If your webhook script performs any logic upon receiving a webhook, you should return a
200
status code before completing the logic to prevent timeouts.
Handling duplicate events
Callback endpoints might occasionally receive the same event more than once. We advise you to guard against duplicated event receipts. One way of doing this is logging the events you’ve processed, and then not processing the logged ones.
Webhook validation
The webhook also contain a signature to allow servers to verify the authenticity of the request using a server-side secret key. The key is provided to you by your account manager. The signature is present in the headers as x-signature
header.
How the signature is calculated
- Concatenate the webhook url, content-type and payload of the webhook
- Generate the signature using the Secret Key and hashedBody components.
Below are examples in some popular programming languages
Be careful!
Make sure you do not parse your HTTP request into an object and then serialize it.
Instead, just get the webhook raw payload as a string.
import org.apache.commons.codec.digest.HmacAlgorithms;
import org.apache.commons.codec.digest.HmacUtils;
import java.net.URI;
import java.net.URISyntaxException;
/**
* This is a utility class used for validating webhooks from the Crypto Payments product.
* It provides a way to verify the authenticity of the incoming webhook request.
*/
public class Main {
/**
* This is the main method which drives the process of webhook validation.
* @param args Unused.
* @throws URISyntaxException If the provided webhook URL is malformed.
*/
public static void main(String[] args) throws URISyntaxException {
//EXAMPLE TEST DATA
/* Secret key that can be found under Settings -> Manage Merchants -> Merchant Name
in your BVNK merchant account
*/
String secretKey = "LWExNWYtYjRjYzJiZjRmZGZhNDU5NmFjNWMtYjhmNS00OTFhLWE0YTAtMWUwZjU5YjAwZjJk";
/* Webhook URL that you've set under Settings -> Manage Merchants -> Webhook URL */
String webhookUrl = "https://webhook.site/23dcv4a41-d4935-4e16-9ed6-ce812f7bddcd8";
/* Content-Type needed for concatenating the values */
String contentType = "application/json";
/* Body of the webhook. Make sure the webhook payload is escaped correctly with no whitespaces*/
String payload = "{\"event\":\"statusChanged\",\"url\":\"https://coindirect-filestore.s3.amazonaws.com/com.bvnk.reports.BackgroundReport/b9ee2544-b838-4c37-9051-3974c4d19fef.csv?X-Amz-Security-Token=IQoJb3JpZ2luX2VjEGAaCWV1LXdlc3QtMSJHMEUCIQChnP0FdjPn0tJ8HMz%2BsKwRgHwvqKO8shW1Fs1R57FfuwIgYHtQLOrJoY2WiKHO3SmAAcVHUqptlzakFz6XM9tkkSMqkwUIqf%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARAFGgwzODU0ODUwMzkxMTEiDAxHTlmQuRKIa3%2FGMSrnBDRX%2BpF1M9F%2FKDbtH3mxQvG1nORC%2BTcxgrNnTFmYcjpr%2BLFpa22PnVrbPXFEaMnsCzySRqgi%2FBz4QNUAZdGvvDZswQFh53a2pC5gmQN3xWd1pzfzkeOaZwVgbyaq1dsTteLQhZdI9lzJG7ceXaLyHWw23P%2B3JocLJKqRp0ugdc3Ksj3RhtgerqxRx80mToq98xB7ndjhoWyVTALQ4Bk7dpBEzoF8ZpQ8zC46uJufwmazn8tZDJK%2Fg603djWfMfCR53lpenLtIxxWD0Feph2IoI5arn2XF%2BZqCg2j8kWAriMYyfOohG1SLcHzjozscsCWNlZgsFJBCypiZltnnUGbPLhUjtodW6a8F7Oib6yB5wEIGcVcvUj1obq%2FL7%2FN78wHaRH5PlzcD%2F9Z%2BoqBPwu%2BKSgl8QTNp%2FG8cO8KwXvm4Gq6%2Fix%2FZMKsfLjPWQ%2BCeZTfm8t9WBFPd5Var89rfZIr26pz5R8XAtIMmVqY8mbLZeDKVem5DwVt1fwBlDb%2BKC9cdduWLfuAFh6kRTG744d71XCetYTw8TUMdZ7emcVHk%2BjHC8qHqwE1bYsG0DAoXJ%2BARW6xwqAxID%2FjkmhJ8Nn%2B07UBtHU6UPhHhCpBLYCu%2B75%2BLwkoqOUWQsm9CJ7ePpDaMT%2B9ovmnolv%2BuRoIhfcKhx3aZSLm2uhrF%2FL4AcdXS%2BNitALmNL5c8F6RX6esQzpuksmLHxq8aFTfz1vEgbklG%2BTES9bsYF8B8vV%2FSNz6VyRohQGGxgEKTgj2%2Bxmr35HpeO86jbz6EU3qHtCF662TCRBlXgnqkQqjzyNVzYrKAu86NFrwIinemjCs9amxBjqbAaIBk%2BWvSyb%2FTVqBq%2FPJSgDaMlmDsWvFMDgY4XcSHx1XuIpTrZxHDErObDG2oHG2VacFUtQw2aeK8OEig94pWd%2B9QhtBiMjbu0LX4IJhX7vNFZ%2BMKC4U7wQJ0lu3SbYwHFNQWWkx07ajTN24DruGQkrbgG%2BzG6u1WoXnr35cUrR0fJUnhqcb0ILVBA7gDpgoW1Xyiz7Bg0mnIKjb&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20240425T155243Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86394&X-Amz-Credential=ASIAVTQFS4YDTMPXPNXR%2F20240425%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=872cc212faa8676dcb492f195ddfd9de054e7cf2024165f0d795f9c5b56e9600\"}";
// Concatenate the webhook URL, Content-Type, and payload
String hashBody = getBodyToHash(webhookUrl, contentType, payload);
// Generate the signature
String signature = generateSignature(secretKey, hashBody);
// Output the signature
System.out.println("Generated signature: " + signature);
}
/**
* This method creates a concatenated string from the provided webhook URL, content type, and payload.
* @param webhookUrl The URL where the webhook will be sent.
* @param contentType The MIME type of the content.
* @param payload The actual content body of the webhook.
* @return A concatenated string of the provided parameters.
* @throws URISyntaxException If the provided webhook URL is malformed.
*/
public static String getBodyToHash(String webhookUrl, String contentType, String payload) throws URISyntaxException {
URI uri = new URI(webhookUrl);
StringBuilder hashBody = new StringBuilder();
hashBody.append(uri.getPath());
if (uri.getRawQuery() != null) {
hashBody.append(uri.getRawQuery());
}
if (contentType != null) {
hashBody.append(contentType);
}
if (payload != null) {
hashBody.append(payload);
}
return hashBody.toString();
}
/**
* This method generates a signature from the provided secret and the hashed body.
* It uses the HMAC-SHA256 hashing algorithm.
* @param secret The secret key used to generate the hash.
* @param hashBody The body that needs to be hashed.
* @return The HMAC-SHA256 hash of the body, encoded as a hexadecimal string.
*/
public static String generateSignature(final String secret, String hashBody) {
return toHex(new HmacUtils(HmacAlgorithms.HMAC_SHA_256, secret).hmac(hashBody));
}
/**
* This is a helper method that converts a byte array to a hexadecimal string.
* @param bytes The byte array that needs to be converted.
* @return The hexadecimal representation of the byte array.
*/
static String toHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}
import hashlib
import hmac
import urllib.parse
def get_body_to_hash(webhook_url, content_type, payload):
"""
This method creates a concatenated string from the provided webhook URL, content type, and payload.
:param webhook_url: The URL where the webhook will be sent.
:param content_type: The MIME type of the content.
:param payload: The actual content body of the webhook.
:return: A concatenated string of the provided parameters.
"""
request_url = urllib.parse.urlparse(webhook_url)
body_to_hash = f"{request_url.path}{content_type}{payload}"
return body_to_hash
def generate_signature(secret_key, hash_body):
"""
This method generates a signature from the provided secret and the hashed body.
It uses the HMAC-SHA256 hashing algorithm.
:param secret_key: The secret key used to generate the hash.
:param hash_body: The body that needs to be hashed.
:return: The HMAC-SHA256 hash of the body, encoded as a hexadecimal string.
"""
hashed = hmac.new(secret_key.encode(), hash_body.encode(), hashlib.sha256)
return hashed.hexdigest()
def validate_webhook():
"""
This function prints out the signature from inputs.
"""
# Secret key that can be found under Settings -> Manage Merchants -> Merchant Name in your BVNK merchant account
secret_key = 'YTczZmM5YTEtZDFhNi00YWU5LTk4ODktMmEzODI1NzBkMDI2ZWFjNDcwNzgtNTRkYi00OTY0LWI5NjAtMzRmOTQ0ZTE5NDQ3'
# Webhook URL that you've set under Settings -> Manage Merchants -> Webhook URL
webhook_url = 'https://webhook.site/11488aba-735e-4906-9809-f1a11a91f49f'
# Content-Type needed for concatenating the values
content_type = 'application/json'
# Body of the webhook.
payload = '{"event":"reportGenerated","url":"https://coindirect-filestore.s3.amazonaws.com/com.bvnk.reports.BackgroundReport/b9ee2544-b838-4c37-9051-3974c4d19fef.csv?X-Amz-Security-Token=IQoJb3JpZ2luX2VjEGAaCWV1LXdlc3QtMSJHMEUCIQChnP0FdjPn0tJ8HMz%2BsKwRgHwvqKO8shW1Fs1R57FfuwIgYHtQLOrJoY2WiKHO3SmAAcVHUqptlzakFz6XM9tkkSMqkwUIqf%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARAFGgwzODU0ODUwMzkxMTEiDAxHTlmQuRKIa3%2FGMSrnBDRX%2BpF1M9F%2FKDbtH3mxQvG1nORC%2BTcxgrNnTFmYcjpr%2BLFpa22PnVrbPXFEaMnsCzySRqgi%2FBz4QNUAZdGvvDZswQFh53a2pC5gmQN3xWd1pzfzkeOaZwVgbyaq1dsTteLQhZdI9lzJG7ceXaLyHWw23P%2B3JocLJKqRp0ugdc3Ksj3RhtgerqxRx80mToq98xB7ndjhoWyVTALQ4Bk7dpBEzoF8ZpQ8zC46uJufwmazn8tZDJK%2Fg603djWfMfCR53lpenLtIxxWD0Feph2IoI5arn2XF%2BZqCg2j8kWAriMYyfOohG1SLcHzjozscsCWNlZgsFJBCypiZltnnUGbPLhUjtodW6a8F7Oib6yB5wEIGcVcvUj1obq%2FL7%2FN78wHaRH5PlzcD%2F9Z%2BoqBPwu%2BKSgl8QTNp%2FG8cO8KwXvm4Gq6%2Fix%2FZMKsfLjPWQ%2BCeZTfm8t9WBFPd5Var89rfZIr26pz5R8XAtIMmVqY8mbLZeDKVem5DwVt1fwBlDb%2BKC9cdduWLfuAFh6kRTG744d71XCetYTw8TUMdZ7emcVHk%2BjHC8qHqwE1bYsG0DAoXJ%2BARW6xwqAxID%2FjkmhJ8Nn%2B07UBtHU6UPhHhCpBLYCu%2B75%2BLwkoqOUWQsm9CJ7ePpDaMT%2B9ovmnolv%2BuRoIhfcKhx3aZSLm2uhrF%2FL4AcdXS%2BNitALmNL5c8F6RX6esQzpuksmLHxq8aFTfz1vEgbklG%2BTES9bsYF8B8vV%2FSNz6VyRohQGGxgEKTgj2%2Bxmr35HpeO86jbz6EU3qHtCF662TCRBlXgnqkQqjzyNVzYrKAu86NFrwIinemjCs9amxBjqbAaIBk%2BWvSyb%2FTVqBq%2FPJSgDaMlmDsWvFMDgY4XcSHx1XuIpTrZxHDErObDG2oHG2VacFUtQw2aeK8OEig94pWd%2B9QhtBiMjbu0LX4IJhX7vNFZ%2BMKC4U7wQJ0lu3SbYwHFNQWWkx07ajTN24DruGQkrbgG%2BzG6u1WoXnr35cUrR0fJUnhqcb0ILVBA7gDpgoW1Xyiz7Bg0mnIKjb&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20240425T155243Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86394&X-Amz-Credential=ASIAVTQFS4YDTMPXPNXR%2F20240425%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=872cc212faa8676dcb492f195ddfd9de054e7cf2024165f0d795f9c5b56e9600"'
# Concatenate the webhook URL, Content-Type, and payload
hash_body = get_body_to_hash(webhook_url, content_type, payload)
# Generate the signature
signature = generate_signature(secret_key, hash_body)
# Output the signature
print(f"Generated signature: {signature}")
validate_webhook()
<?php
/**
* This method creates a concatenated string from the provided webhook URL, content type, and payload.
* @param string $webhookUrl The URL where the webhook will be sent.
* @param string $contentType The MIME type of the content.
* @param string $payload The actual content body of the webhook.
* @return string A concatenated string of the provided parameters.
*/
function getBodyToHash($webhookUrl, $contentType, $payload) {
$requestURL = parse_url($webhookUrl);
$bodyToHash = $requestURL['path'] . $contentType . $payload;
return $bodyToHash;
}
/**
* This method generates a signature from the provided secret and the hashed body.
* It uses the HMAC-SHA256 hashing algorithm.
* @param string $secret The secret key used to generate the hash.
* @param string $hashBody The body that needs to be hashed.
* @return string The HMAC-SHA256 hash of the body, encoded as a hexadecimal string.
*/
function generateSignature($secretKey, $hashBody) {
$hash = hash_hmac('sha256', $hashBody, $secretKey);
return $hash;
}
/**
* This function prints out the signature from inputs.
*/
function validateWebhook() {
// EXAMPLE TEST DATA
$secretKey = 'YTczZmM5YTEtZDFhNi00YWU5LTk4ODktMmEzODI1NzBkMDI2ZWFjNDcwNzgtNTRkYi00OTY0LWI5NjAtMzRmOTQ0ZTE5NDQ3';
$webhookUrl = 'https://webhook.site/11488aba-735e-4906-9809-f1a11a91f49f';
$contentType = 'application/json';
$payload = '{"event":"reportGenerated","url":"https://coindirect-filestore.s3.amazonaws.com/com.bvnk.reports.BackgroundReport/b9ee2544-b838-4c37-9051-3974c4d19fef.csv?X-Amz-Security-Token=IQoJb3JpZ2luX2VjEGAaCWV1LXdlc3QtMSJHMEUCIQChnP0FdjPn0tJ8HMz%2BsKwRgHwvqKO8shW1Fs1R57FfuwIgYHtQLOrJoY2WiKHO3SmAAcVHUqptlzakFz6XM9tkkSMqkwUIqf%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARAFGgwzODU0ODUwMzkxMTEiDAxHTlmQuRKIa3%2FGMSrnBDRX%2BpF1M9F%2FKDbtH3mxQvG1nORC%2BTcxgrNnTFmYcjpr%2BLFpa22PnVrbPXFEaMnsCzySRqgi%2FBz4QNUAZdGvvDZswQFh53a2pC5gmQN3xWd1pzfzkeOaZwVgbyaq1dsTteLQhZdI9lzJG7ceXaLyHWw23P%2B3JocLJKqRp0ugdc3Ksj3RhtgerqxRx80mToq98xB7ndjhoWyVTALQ4Bk7dpBEzoF8ZpQ8zC46uJufwmazn8tZDJK%2Fg603djWfMfCR53lpenLtIxxWD0Feph2IoI5arn2XF%2BZqCg2j8kWAriMYyfOohG1SLcHzjozscsCWNlZgsFJBCypiZltnnUGbPLhUjtodW6a8F7Oib6yB5wEIGcVcvUj1obq%2FL7%2FN78wHaRH5PlzcD%2F9Z%2BoqBPwu%2BKSgl8QTNp%2FG8cO8KwXvm4Gq6%2Fix%2FZMKsfLjPWQ%2BCeZTfm8t9WBFPd5Var89rfZIr26pz5R8XAtIMmVqY8mbLZeDKVem5DwVt1fwBlDb%2BKC9cdduWLfuAFh6kRTG744d71XCetYTw8TUMdZ7emcVHk%2BjHC8qHqwE1bYsG0DAoXJ%2BARW6xwqAxID%2FjkmhJ8Nn%2B07UBtHU6UPhHhCpBLYCu%2B75%2BLwkoqOUWQsm9CJ7ePpDaMT%2B9ovmnolv%2BuRoIhfcKhx3aZSLm2uhrF%2FL4AcdXS%2BNitALmNL5c8F6RX6esQzpuksmLHxq8aFTfz1vEgbklG%2BTES9bsYF8B8vV%2FSNz6VyRohQGGxgEKTgj2%2Bxmr35HpeO86jbz6EU3qHtCF662TCRBlXgnqkQqjzyNVzYrKAu86NFrwIinemjCs9amxBjqbAaIBk%2BWvSyb%2FTVqBq%2FPJSgDaMlmDsWvFMDgY4XcSHx1XuIpTrZxHDErObDG2oHG2VacFUtQw2aeK8OEig94pWd%2B9QhtBiMjbu0LX4IJhX7vNFZ%2BMKC4U7wQJ0lu3SbYwHFNQWWkx07ajTN24DruGQkrbgG%2BzG6u1WoXnr35cUrR0fJUnhqcb0ILVBA7gDpgoW1Xyiz7Bg0mnIKjb&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20240425T155243Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86394&X-Amz-Credential=ASIAVTQFS4YDTMPXPNXR%2F20240425%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=872cc212faa8676dcb492f195ddfd9de054e7cf2024165f0d795f9c5b56e9600"}';
// Concatenate the webhook URL, Content-Type, and payload
$hashBody = getBodyToHash($webhookUrl, $contentType, $payload);
// Generate the signature
$signature = generateSignature($secretKey, $hashBody);
// Output the signature
echo "Generated signature: " . $signature;
}
validateWebhook();
?>
const CryptoJS = require("crypto-js");
/**
* This method creates a concatenated string from the provided webhook URL, content type, and payload.
* @param webhookUrl The URL where the webhook will be sent.
* @param contentType The MIME type of the content.
* @param payload The actual content body of the webhook.
* @return A concatenated string of the provided parameters.
*/
function getBodyToHash(webhookUrl, contentType, payload) {
const requestURL = new URL(webhookUrl);
const bodyToHash = `${requestURL.pathname}${contentType}${payload}`;
return bodyToHash;
}
/**
* This method generates a signature from the provided secret and the hashed body.
* It uses the HMAC-SHA256 hashing algorithm.
* @param secret The secret key used to generate the hash.
* @param hashBody The body that needs to be hashed.
* @return The HMAC-SHA256 hash of the body, encoded as a hexadecimal string.
*/
function generateSignature(secretKey, hashBody) {
const hasher = CryptoJS.HmacSHA256(hashBody, secretKey);
const hashInHex = CryptoJS.enc.Hex.stringify(hasher);
return hashInHex;
}
/**
* This function prints out the signature from inputs.
*/
function validateWebhook(){
// EXAMPLE TEST DATA
// Secret key that can be found under Settings -> Manage Merchants -> Merchant Name in your BVNK merchant account
const secretKey = 'YTczZmM5YTEtZDFhNi00YWU5LTk4ODktMmEzODI1NzBkMDI2ZWFjNDcwNzgtNTRkYi00OTY0LWI5NjAtMzRmOTQ0ZTE5NDQ3';
// Webhook URL that you've set under Settings -> Manage Merchants -> Webhook URL
const webhookUrl = 'https://webhook.site/11488aba-735e-4906-9809-f1a11a91f49f';
// Content-Type needed for concatenating the values
const contentType = 'application/json';
// Body of the webhook.
const payload = '{"event":"reportGenerated","url":"https://coindirect-filestore.s3.amazonaws.com/com.bvnk.reports.BackgroundReport/b9ee2544-b838-4c37-9051-3974c4d19fef.csv?X-Amz-Security-Token=IQoJb3JpZ2luX2VjEGAaCWV1LXdlc3QtMSJHMEUCIQChnP0FdjPn0tJ8HMz%2BsKwRgHwvqKO8shW1Fs1R57FfuwIgYHtQLOrJoY2WiKHO3SmAAcVHUqptlzakFz6XM9tkkSMqkwUIqf%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARAFGgwzODU0ODUwMzkxMTEiDAxHTlmQuRKIa3%2FGMSrnBDRX%2BpF1M9F%2FKDbtH3mxQvG1nORC%2BTcxgrNnTFmYcjpr%2BLFpa22PnVrbPXFEaMnsCzySRqgi%2FBz4QNUAZdGvvDZswQFh53a2pC5gmQN3xWd1pzfzkeOaZwVgbyaq1dsTteLQhZdI9lzJG7ceXaLyHWw23P%2B3JocLJKqRp0ugdc3Ksj3RhtgerqxRx80mToq98xB7ndjhoWyVTALQ4Bk7dpBEzoF8ZpQ8zC46uJufwmazn8tZDJK%2Fg603djWfMfCR53lpenLtIxxWD0Feph2IoI5arn2XF%2BZqCg2j8kWAriMYyfOohG1SLcHzjozscsCWNlZgsFJBCypiZltnnUGbPLhUjtodW6a8F7Oib6yB5wEIGcVcvUj1obq%2FL7%2FN78wHaRH5PlzcD%2F9Z%2BoqBPwu%2BKSgl8QTNp%2FG8cO8KwXvm4Gq6%2Fix%2FZMKsfLjPWQ%2BCeZTfm8t9WBFPd5Var89rfZIr26pz5R8XAtIMmVqY8mbLZeDKVem5DwVt1fwBlDb%2BKC9cdduWLfuAFh6kRTG744d71XCetYTw8TUMdZ7emcVHk%2BjHC8qHqwE1bYsG0DAoXJ%2BARW6xwqAxID%2FjkmhJ8Nn%2B07UBtHU6UPhHhCpBLYCu%2B75%2BLwkoqOUWQsm9CJ7ePpDaMT%2B9ovmnolv%2BuRoIhfcKhx3aZSLm2uhrF%2FL4AcdXS%2BNitALmNL5c8F6RX6esQzpuksmLHxq8aFTfz1vEgbklG%2BTES9bsYF8B8vV%2FSNz6VyRohQGGxgEKTgj2%2Bxmr35HpeO86jbz6EU3qHtCF662TCRBlXgnqkQqjzyNVzYrKAu86NFrwIinemjCs9amxBjqbAaIBk%2BWvSyb%2FTVqBq%2FPJSgDaMlmDsWvFMDgY4XcSHx1XuIpTrZxHDErObDG2oHG2VacFUtQw2aeK8OEig94pWd%2B9QhtBiMjbu0LX4IJhX7vNFZ%2BMKC4U7wQJ0lu3SbYwHFNQWWkx07ajTN24DruGQkrbgG%2BzG6u1WoXnr35cUrR0fJUnhqcb0ILVBA7gDpgoW1Xyiz7Bg0mnIKjb&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20240425T155243Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86394&X-Amz-Credential=ASIAVTQFS4YDTMPXPNXR%2F20240425%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=872cc212faa8676dcb492f195ddfd9de054e7cf2024165f0d795f9c5b56e9600"}';
// Concatenate the webhook URL, Content-Type, and payload
let hashBody = getBodyToHash(webhookUrl, contentType, payload);
// Generate the signature
let signature = generateSignature(secretKey, hashBody);
// Output the signature
console.log(`Generated signature: ${signature}`);
}
validateWebhook();
using System;
using System.Security.Cryptography;
using System.Text;
using System.Web;
class Program
{
static void Main()
{
ValidateWebhook();
}
/*
* This method creates a concatenated string from the provided webhook URL, content type, and payload.
*/
static string GetBodyToHash(string webhookUrl, string contentType, string payload)
{
Uri requestUrl = new Uri(webhookUrl);
string bodyToHash = $"{requestUrl.AbsolutePath}{contentType}{payload}";
return bodyToHash;
}
/*
* This method generates a signature from the provided secret and the hashed body.
* It uses the HMAC-SHA256 hashing algorithm.
*/
static string GenerateSignature(string secretKey, string hashBody)
{
using (var hmac = new HMACSHA256(Encoding.ASCII.GetBytes(secretKey)))
{
var hash = hmac.ComputeHash(Encoding.ASCII.GetBytes(hashBody));
return BitConverter.ToString(hash).Replace("-", "").ToLower();
}
}
/*
* This function prints out the signature from inputs.
*/
static void ValidateWebhook()
{
// Secret key that can be found under Settings -> Manage Merchants -> Merchant Name in your BVNK merchant account
const string secretKey = "YTczZmM5YTEtZDFhNi00YWU5LTk4ODktMmEzODI1NzBkMDI2ZWFjNDcwNzgtNTRkYi00OTY0LWI5NjAtMzRmOTQ0ZTE5NDQ3";
// Webhook URL that you've set under Settings -> Manage Merchants -> Webhook URL
const string webhookUrl = "https://webhook.site/11488aba-735e-4906-9809-f1a11a91f49f";
// Content-Type needed for concatenating the values
const string contentType = "application/json";
// Body of the webhook.
const string payload = "{\"event\":\"reportGenerated\",\"url\":\"https://coindirect-filestore.s3.amazonaws.com/com.bvnk.reports.BackgroundReport/b9ee2544-b838-4c37-9051-3974c4d19fef.csv?X-Amz-Security-Token=IQoJb3JpZ2luX2VjEGAaCWV1LXdlc3QtMSJHMEUCIQChnP0FdjPn0tJ8HMz%2BsKwRgHwvqKO8shW1Fs1R57FfuwIgYHtQLOrJoY2WiKHO3SmAAcVHUqptlzakFz6XM9tkkSMqkwUIqf%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARAFGgwzODU0ODUwMzkxMTEiDAxHTlmQuRKIa3%2FGMSrnBDRX%2BpF1M9F%2FKDbtH3mxQvG1nORC%2BTcxgrNnTFmYcjpr%2BLFpa22PnVrbPXFEaMnsCzySRqgi%2FBz4QNUAZdGvvDZswQFh53a2pC5gmQN3xWd1pzfzkeOaZwVgbyaq1dsTteLQhZdI9lzJG7ceXaLyHWw23P%2B3JocLJKqRp0ugdc3Ksj3RhtgerqxRx80mToq98xB7ndjhoWyVTALQ4Bk7dpBEzoF8ZpQ8zC46uJufwmazn8tZDJK%2Fg603djWfMfCR53lpenLtIxxWD0Feph2IoI5arn2XF%2BZqCg2j8kWAriMYyfOohG1SLcHzjozscsCWNlZgsFJBCypiZltnnUGbPLhUjtodW6a8F7Oib6yB5wEIGcVcvUj1obq%2FL7%2FN78wHaRH5PlzcD%2F9Z%2BoqBPwu%2BKSgl8QTNp%2FG8cO8KwXvm4Gq6%2Fix%2FZMKsfLjPWQ%2BCeZTfm8t9WBFPd5Var89rfZIr26pz5R8XAtIMmVqY8mbLZeDKVem5DwVt1fwBlDb%2BKC9cdduWLfuAFh6kRTG744d71XCetYTw8TUMdZ7emcVHk%2BjHC8qHqwE1bYsG0DAoXJ%2BARW6xwqAxID%2FjkmhJ8Nn%2B07UBtHU6UPhHhCpBLYCu%2B75%2BLwkoqOUWQsm9CJ7ePpDaMT%2B9ovmnolv%2BuRoIhfcKhx3aZSLm2uhrF%2FL4AcdXS%2BNitALmNL5c8F6RX6esQzpuksmLHxq8aFTfz1vEgbklG%2BTES9bsYF8B8vV%2FSNz6VyRohQGGxgEKTgj2%2Bxmr35HpeO86jbz6EU3qHtCF662TCRBlXgnqkQqjzyNVzYrKAu86NFrwIinemjCs9amxBjqbAaIBk%2BWvSyb%2FTVqBq%2FPJSgDaMlmDsWvFMDgY4XcSHx1XuIpTrZxHDErObDG2oHG2VacFUtQw2aeK8OEig94pWd%2B9QhtBiMjbu0LX4IJhX7vNFZ%2BMKC4U7wQJ0lu3SbYwHFNQWWkx07ajTN24DruGQkrbgG%2BzG6u1WoXnr35cUrR0fJUnhqcb0ILVBA7gDpgoW1Xyiz7Bg0mnIKjb&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20240425T155243Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86394&X-Amz-Credential=ASIAVTQFS4YDTMPXPNXR%2F20240425%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=872cc212faa8676dcb492f195ddfd9de054e7cf2024165f0d795f9c5b56e9600\"}";
// Concatenate the webhook URL, Content-Type, and payload
string hashBody = GetBodyToHash(webhookUrl, contentType, payload);
// Generate the signature
string signature = GenerateSignature(secretKey, hashBody);
// Output the signature
Console.WriteLine($"Generated signature: {signature}");
}
}
require 'openssl'
require 'uri'
require 'json'
# This method creates a concatenated string from the provided webhook URL, content type, and payload.
# @param webhook_url [String] The URL where the webhook will be sent.
# @param content_type [String] The MIME type of the content.
# @param payload [String] The actual content body of the webhook.
# @return [String] A concatenated string of the provided parameters.
def get_body_to_hash(webhook_url, content_type, payload)
request_url = URI.parse(webhook_url)
body_to_hash = "#{request_url.path}#{content_type}#{payload}"
return body_to_hash
end
# This method generates a signature from the provided secret and the hashed body.
# It uses the HMAC-SHA256 hashing algorithm.
# @param secret_key [String] The secret key used to generate the hash.
# @param hash_body [String] The body that needs to be hashed.
# @return [String] The HMAC-SHA256 hash of the body, encoded as a hexadecimal string.
def generate_signature(secret_key, hash_body)
digest = OpenSSL::Digest.new('sha256')
hmac = OpenSSL::HMAC.hexdigest(digest, secret_key, hash_body)
return hmac
end
# This function prints out the signature from inputs.
def validate_webhook
# EXAMPLE TEST DATA
# Secret key that can be found under Settings -> Manage Merchants -> Merchant Name in your BVNK merchant account
secret_key = 'YTczZmM5YTEtZDFhNi00YWU5LTk4ODktMmEzODI1NzBkMDI2ZWFjNDcwNzgtNTRkYi00OTY0LWI5NjAtMzRmOTQ0ZTE5NDQ3'
# Webhook URL that you've set under Settings -> Manage Merchants -> Webhook URL
webhook_url = 'https://webhook.site/11488aba-735e-4906-9809-f1a11a91f49f'
# Content-Type needed for concatenating the values
content_type = 'application/json'
# Body of the webhook.
payload = '{"event":"reportGenerated","url":"https://coindirect-filestore.s3.amazonaws.com/com.bvnk.reports.BackgroundReport/b9ee2544-b838-4c37-9051-3974c4d19fef.csv?X-Amz-Security-Token=IQoJb3JpZ2luX2VjEGAaCWV1LXdlc3QtMSJHMEUCIQChnP0FdjPn0tJ8HMz%2BsKwRgHwvqKO8shW1Fs1R57FfuwIgYHtQLOrJoY2WiKHO3SmAAcVHUqptlzakFz6XM9tkkSMqkwUIqf%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARAFGgwzODU0ODUwMzkxMTEiDAxHTlmQuRKIa3%2FGMSrnBDRX%2BpF1M9F%2FKDbtH3mxQvG1nORC%2BTcxgrNnTFmYcjpr%2BLFpa22PnVrbPXFEaMnsCzySRqgi%2FBz4QNUAZdGvvDZswQFh53a2pC5gmQN3xWd1pzfzkeOaZwVgbyaq1dsTteLQhZdI9lzJG7ceXaLyHWw23P%2B3JocLJKqRp0ugdc3Ksj3RhtgerqxRx80mToq98xB7ndjhoWyVTALQ4Bk7dpBEzoF8ZpQ8zC46uJufwmazn8tZDJK%2Fg603djWfMfCR53lpenLtIxxWD0Feph2IoI5arn2XF%2BZqCg2j8kWAriMYyfOohG1SLcHzjozscsCWNlZgsFJBCypiZltnnUGbPLhUjtodW6a8F7Oib6yB5wEIGcVcvUj1obq%2FL7%2FN78wHaRH5PlzcD%2F9Z%2BoqBPwu%2BKSgl8QTNp%2FG8cO8KwXvm4Gq6%2Fix%2FZMKsfLjPWQ%2BCeZTfm8t9WBFPd5Var89rfZIr26pz5R8XAtIMmVqY8mbLZeDKVem5DwVt1fwBlDb%2BKC9cdduWLfuAFh6kRTG744d71XCetYTw8TUMdZ7emcVHk%2BjHC8qHqwE1bYsG0DAoXJ%2BARW6xwqAxID%2FjkmhJ8Nn%2B07UBtHU6UPhHhCpBLYCu%2B75%2BLwkoqOUWQsm9CJ7ePpDaMT%2B9ovmnolv%2BuRoIhfcKhx3aZSLm2uhrF%2FL4AcdXS%2BNitALmNL5c8F6RX6esQzpuksmLHxq8aFTfz1vEgbklG%2BTES9bsYF8B8vV%2FSNz6VyRohQGGxgEKTgj2%2Bxmr35HpeO86jbz6EU3qHtCF662TCRBlXgnqkQqjzyNVzYrKAu86NFrwIinemjCs9amxBjqbAaIBk%2BWvSyb%2FTVqBq%2FPJSgDaMlmDsWvFMDgY4XcSHx1XuIpTrZxHDErObDG2oHG2VacFUtQw2aeK8OEig94pWd%2B9QhtBiMjbu0LX4IJhX7vNFZ%2BMKC4U7wQJ0lu3SbYwHFNQWWkx07ajTN24DruGQkrbgG%2BzG6u1WoXnr35cUrR0fJUnhqcb0ILVBA7gDpgoW1Xyiz7Bg0mnIKjb&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20240425T155243Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86394&X-Amz-Credential=ASIAVTQFS4YDTMPXPNXR%2F20240425%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=872cc212faa8676dcb492f195ddfd9de054e7cf2024165f0d795f9c5b56e9600"}'
# Concatenate the webhook URL, Content-Type, and payload
hash_body = get_body_to_hash(webhook_url, content_type, payload)
# Generate the signature
signature = generate_signature(secret_key, hash_body)
# Output the signature
puts "Generated signature: #{signature}"
end
validate_webhook
The signature will be available in the header of the webhook, x-signature
.
Every new webhook will have a new value of the x-signature header. Make sure you are comparing the hash to the right header.
Updated 7 days ago