Authenticating Notification Webhooks
Tatum notification service supports HMAC to authenticate webhooks.
HMAC allows you to authenticate the legitimacy of notifications sent to your URL.
Steps
Step_1: Enable HMAC
Example request:
- The param “
hmacSecret
” is provided by the user to authenticate webhook origin.
curl --location --request PUT 'https://api-eu1.tatum.io/v4/subscription' \
--header 'Content-Type: application/json' \
--header 'x-api-key: {API_KEY} ' \
--data '{
"hmacSecret": "c354b83b-d31b-4dda-9bab-d6a67715a1ed"
}'
// Response
204 No Content
Step_2: Create a subscription
Example request:
curl --location 'https://api-eu1.tatum.io/v4/subscription' \
--header 'Content-Type: application/json' \
--header 'x-api-key: {API_KEY} ' \
--data '{
"type": "ADDRESS_EVENT",
"attr": {
"chain": "TRON",
"address": "TVf3RVEtzKtMfqQaCAWs9d4HKbC4bZGaWP",
"url": "https://eo1tfamse2vfgpm.m.pipedream.net"
}
}'
// Response
{
"id": "6644ce703ac81e0b215820cc"
}
Step_3: Check the webhook
Newly fired webhooks by Tatum should contain a x-payload-hash
in the header.
Example webhook:
// Example Webhook headers and body
{
"event": {
"method": "POST",
"url": "https://eo1tfamse2vfgpm.m.pipedream.net/",
"headers": {
"host": "eo1tfamse2vfgpm.m.pipedream.net",
"content-length": "317",
"accept": "application/json, text/plain, */*",
"content-type": "application/json",
"x-payload-hash": "WdhYQft+qP8LpYAdeOMncUzIZ7DSUWX9JVSjeGH3F4mCreUxtIpTl2VYigm+qUvkfSQ0lWmTrzADm4mGxSVcxA=="
},
"body": {
"address": "TJG7iciLGjsib9qhe6U6F7M2vxYJuDjWNM",
"amount": "20",
"counterAddress": "TVf3RVEtzKtMfqQaCAWs9d4HKbC4bZGaWP",
"asset": "TRON",
"blockNumber": 44087791,
"txId": "93442189d7bccbe009f8ab594831ff9d7d258cab712d74a404cd3dccdc4c6d69",
"type": "native",
"tokenId": null,
"chain": "tron-testnet",
"subscriptionType": "ADDRESS_EVENT"
}
}
}
Step_4: Verify the Webhook Authenticity
- Make sure HMAC is enabled. See Step_1 above.
- From the received webhooks, retrieve the
x-payload-hash
value from the header and the webhook's payload as JSON- Example x-payload-hash:
"x-payload-hash": "WdhYQft+qP8LpYAdeOMncUzIZ7DSUWX9JVSjeGH3F4mCreUxtIpTl2VYigm+qUvkfSQ0lWmTrzADm4mGxSVcxA=="
- Example x-payload-hash:
- Convert the webhook payload to stringify JSON without any spaces.
- In JavaScript, you would do it like this
JSON.stringify(webhook.event.body)
- In JavaScript, you would do it like this
- Perform calculations on your side to create a digest using the HMAC Secret, the webhook payload, and the HMAC SHA512 algorithm.
- Compare
x-payload-hash
header value with calculated digest as aBase64 string
. Both values must match to confirm that the webhooks are being received from Tatum.
Example code:
const { createHmac } = require('node:crypto');
// Step 1: Enable HMAC
// Same HMAC Secret as the one used in the "Enable HMAC" endpoint
const hmacSecret = "c354b83b-d31b-4dda-9bab-d6a67715a1ed";
// Step 2: Get webhook payload and x-payload-hash
const xPayloadHash =
"WdhYQft+qP8LpYAdeOMncUzIZ7DSUWX9JVSjeGH3F4mCreUxtIpTl2VYigm+qUvkfSQ0lWmTrzADm4mGxSVcxA==";
const webhook = {
body: {
address: "TJG7iciLGjsib9qhe6U6F7M2vxYJuDjWNM",
amount: "20",
counterAddress: "TVf3RVEtzKtMfqQaCAWs9d4HKbC4bZGaWP",
asset: "TRON",
blockNumber: 44087791,
txId: "93442189d7bccbe009f8ab594831ff9d7d258cab712d74a404cd3dccdc4c6d69",
type: "native",
tokenId: null,
chain: "tron-testnet",
subscriptionType: "ADDRESS_EVENT",
},
};
// Step 3: Convert webhook body to stringify JSON
const stringifyWebhook = JSON.stringify(webhook.body);
// Step 4: Calculate digest as a Base64 string using the HMAC Secret, the webhook payload, and the HMAC SHA512 algorithm.
const base64Hash = createHmac("sha512", hmacSecret)
.update(JSON.stringify(webhook.body))
.digest("base64");
// Step 5: Compare x-payload-hash value with calculated digest as a Base64 string
const checkValues = xPayloadHash == base64Hash;
console.log(`base64Hash: ${base64Hash}`);
console.log(`x-payload-hash and base64Hash are equal? ${checkValues}`);
// Output
/*
base64Hash: WdhYQft+qP8LpYAdeOMncUzIZ7DSUWX9JVSjeGH3F4mCreUxtIpTl2VYigm+qUvkfSQ0lWmTrzADm4mGxSVcxA==
x-payload-hash and base64Hash are equal? true
*/
const { createHmac } = await import("node:crypto");
// Step 1: Enable HMAC
// Same HMAC Secret as the one used in the "Enable HMAC" endpoint
const hmacSecret = "c354b83b-d31b-4dda-9bab-d6a67715a1ed";
// Step 2: Get webhook payload and x-payload-hash
const xPayloadHash =
"WdhYQft+qP8LpYAdeOMncUzIZ7DSUWX9JVSjeGH3F4mCreUxtIpTl2VYigm+qUvkfSQ0lWmTrzADm4mGxSVcxA==";
const webhook = {
body: {
address: "TJG7iciLGjsib9qhe6U6F7M2vxYJuDjWNM",
amount: "20",
counterAddress: "TVf3RVEtzKtMfqQaCAWs9d4HKbC4bZGaWP",
asset: "TRON",
blockNumber: 44087791,
txId: "93442189d7bccbe009f8ab594831ff9d7d258cab712d74a404cd3dccdc4c6d69",
type: "native",
tokenId: null,
chain: "tron-testnet",
subscriptionType: "ADDRESS_EVENT",
},
};
// Step 3: Convert webhook body to stringify JSON
const stringifyWebhook = JSON.stringify(webhook.body);
// Step 4: Calculate digest as a Base64 string using the HMAC Secret, the webhook payload, and the HMAC SHA512 algorithm.
const base64Hash = createHmac("sha512", hmacSecret)
.update(JSON.stringify(webhook.body))
.digest("base64");
// Step 5: Compare x-payload-hash value with calculated digest as a Base64 string
const checkValues = xPayloadHash == base64Hash;
console.log(`base64Hash: ${base64Hash}`);
console.log(`x-payload-hash and base64Hash are equal? ${checkValues}`);
// Output
/*
base64Hash: WdhYQft+qP8LpYAdeOMncUzIZ7DSUWX9JVSjeGH3F4mCreUxtIpTl2VYigm+qUvkfSQ0lWmTrzADm4mGxSVcxA==
x-payload-hash and base64Hash are equal? true
*/
Updated about 1 month ago