Authenticating Notification Webhooks
The Tatum notification service supports HMAC to authenticate webhooks. HMAC will let you authenticate the legitimacy of notifications sent to your URL.
HMAC is enabled on API key level.
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 3 months ago