Using Webhooks

Listen for events on the Cardano network and automatically trigger reactions.

What are Webhooks?

A webhook (also called a web callback or HTTP push API) is a way for an app to provide other applications with real-time information. A webhook delivers data to other applications as it happens, meaning you get data immediately. Unlike typical APIs where you would need to poll for data very frequently in order to get it real-time. This makes webhooks much more efficient for both provider and consumer. Webhooks work by registering a URL to send notifications once certain events occur.
You can think that Webhooks are like a phone number that Tangocrypto calls to notify you of activity in Cardano. The activity could be a payment to an address or reaching a particular epoch. The webhook endpoint is the person answering that call who takes actions based upon the specific information it receives.
A webhook endpoint is just more code on your server, which could be written in Node.js, Go, Java, Ruby, or whatever. The webhook endpoint has an associated URL (e.g. https://myserver.com/callback). The Tangocrypto notifications are Event objects. This Event object contains all the relevant information about what just happened, including the type of event and the data associated with that event. The webhook endpoint uses the event details to take any required actions, such as indicating that an NFT should be sent to a wallet.

Webhooks vs. WebSockets:

The difference between webhooks and WebSockets is that webhooks can only facilitate one-way communication between two services, while WebSockets can facilitate two-way communication between a user and a service, recognizing events and displaying them to the user as they occur.

Types of Webhooks

Tangocrypto offers 4 different types of webhooks:
Callback requests sent from Tangocrypto are always POST and it contains a JSON payload;

Event standard structure

The Event structure always begins with the following parameters:
"id": "2921e3df-c671-4d20-b51b-d176d5c1e43g", //** Unique uuid per event .**
"api_version": "1.0", //**Represents the current Tangocrypto API version, which is v1.**
"webhook_id": "d012a60eccb54c2ba97484f98137be56", // identifies the webhook
"idempotency_key": "3b3359d0ccdb1d3d3ca8dbaa79cb5395b33c5bc52d782f3ea22904abef45d1j4", //**Specifies a unique ID used by Tangocrypto to recognize consecutive requests with the same data so that not to perform the same operation twice.**
"object": "event",
"create_date": 1633954086377,
"type": "payment", // event type


Unique identifier per Event.


The event scheme you receive depends on the version of the Tangocrypto API. Currently, we use v1. When you set a subscription for an event while using v1 of the API, the callback will be returned according to v1 specifics.
Each time we update our API to the next version you will have to reset your Event subscriptions so that they correspond to the newest version currently in use. To do that you'll need to remove the event subscription and set it up again. Otherwise, the callback response will be received in the format of the older API version it was set up in.


The webhook_id indicates a reference to the webhook and It's is a unique code. Each time you set up an event subscription, the corresponding Event always has a parameter webhook_id.


Idempotency represents a process in computing and REST that a server uses to recognise subsequent retries of the same request where the returned result always remains the same. It is a security mechanism for retrying requests without the risk of performing the same operation more than once.
Such risks usually can occur when an API call is for some reason disrupted during processing (e.g. network connection error) and a response is not returned. In such cases, the API call would be retried. By including an idempotency_id in the initial request there is a guarantee that the specific action won’t be done more than once.
The idempotency_id is generated only by Tangocrypto webhooks. It is added to the Event and is unique per triggered webhook.

1. Payment

The Payment Webhook allows you to track payments to an address. This provides your app with real-time state changes when an address sends or receives tokens.
Payload example
"id": "3c23ff25-481c-4e3e-859b-f515135a49b0",
"data": {
"transaction": {
"id": "3776000",
"fee": "168317",
"hash": "e29b4f5e2650560ac61dfa3ccf311e020782d8ccdf295dbbf1cfe2e65583d417",
"size": 289,
"block": {
"id": "3372157",
"fees": "2104143",
"hash": "7fac4956202395c06028b442faba4f3fda68490e2eb7373bd9d0b7b212ff9e1f",
"pool": {
"url": "https://my-ip.at/atada.testnet-metadata-2.json",
"hash": "b4fba3c5a430634f2e5e7007b33be02562efbcd036c0cf3dbb9d9dbdf418ef27",
"name": "ATADA TestnetPool Austria",
"ticker": "ATADA",
"pool_id": "pool18yslg3q320jex6gsmetukxvzm7a20qd90wsll9anlkrfua38flr",
"homepage": "https://stakepool.at",
"description": "Testnet-Environment Pool ..."
"size": 6561,
"time": "Thu Feb 24 2022 12:52:38 GMT+0000 (Coordinated Universal Time)",
"op_cert": "f9096c23c3a3d8afd8d05467fed2bc75405cdbc27ba2106b55a585e414d26573",
"out_sum": "9793211682245",
"slot_no": 51337942,
"vrf_key": "vrf_vk1sleujze3zraykllkafvrxggcmpts3hp6zxrpazdkdzp9g07kkehsnmy8ka",
"block_no": 3345852,
"epoch_no": 189,
"tx_count": "11",
"next_block": null,
"slot_leader": "pool18yslg3q320jex6gsmetukxvzm7a20qd90wsll9anlkrfua38flr",
"confirmations": 1,
"epoch_slot_no": 59542,
"previous_block": 3345851
"deposit": "0",
"out_sum": "948312856",
"block_id": "3372157",
"block_index": 2,
"script_size": 0,
"invalid_before": null,
"valid_contract": true,
"invalid_hereafter": "51359405"
"from": [{
"address": "addr_test1qqvelqlqk94qm9syd40mpqkvdvk0z8ka8mt7e2sfcrq07rmazcna98r9s350vpnyghfsuqk2y29yq88tdcvwm8j0p5dqsg32es",
"hash": "d6ef469d198fbf62a5b9860ba9295b9c9fddb80078e975ba032653f66b070b51",
"index": 1,
"value": "948481173",
"smart_contract": false,
"assets": []
"to": [{
"address": "addr_test1qz5xdujk7unjmyrvqazy7l4w9dzxxfgt48ppv9tsjwywrzckyjqzaxt9rkqxc62m7tcdfylykzzjktqzlwssxfl3mlyqafvh99",
"hash": "e29b4f5e2650560ac61dfa3ccf311e020782d8ccdf295dbbf1cfe2e65583d417",
"index": 0,
"value": "2564320",
"smart_contract": false,
"assets": []
"address": "addr_test1qqvelqlqk94qm9syd40mpqkvdvk0z8ka8mt7e2sfcrq07rmazcna98r9s350vpnyghfsuqk2y29yq88tdcvwm8j0p5dqsg32es",
"hash": "e29b4f5e2650560ac61dfa3ccf311e020782d8ccdf295dbbf1cfe2e65583d417",
"index": 1,
"value": "945748536",
"smart_contract": false,
"assets": []
"type": "payment",
"object": "event",
"webhook_id": "532ce2beb2aa42738e1cc9c5f708168c",
"api_version": "1.0",
"create_date": 1645707159923,
"idempotency_key": "755a42b339274829aefd153285084132532ce2beb2aa42738e1cc9c5f708168c",
"network": "testnet"

2. Block

This event is triggered every time a new block is created.
Payload example
"id": "7b7c0d8a-8885-46d6-8e05-0d0802d95473",
"data": {
"id": "6864165",
"fees": "17182282",
"hash": "641aa7bcd185e036d6a379d4908639d436a540158d1db6debd0e2c3b2fa7c8cd",
"pool": {
"url": "https://ccwallet.io/ccw.metadata.210713.json",
"hash": "924ec324a9d2d172cd3fe44fbbb526e5c6bea677fb7276f07387c847dfe9026d",
"name": "TITANstaking #2",
"ticker": "TITAN",
"pool_id": "pool19pyfv4xnln8x4l7auw0n0skk3hd97shun707hrw5d4s553ys74x",
"homepage": "https://www.titanstaking.io",
"description": "For a TITAN strong Cardano network. Based in Germany. 💪 Join us! Telegram: https://t.me/titanstakingio - Twitter: https://twitter.com/titanstaking"
"size": 58970,
"time": "Fri Feb 04 2022 11:45:09 GMT+0000 (Coordinated Universal Time)",
"op_cert": "400345da097b2eb0194b4a76f87b6853b07e8b96b5de30b671b0e83c54530cd3",
"out_sum": "10738455237",
"slot_no": 52408818,
"vrf_key": "vrf_vk19kgvazgrvr9gstsk2qn0vz0hc9x8yn3lqdymzgztm92qk6r4q9asksen0h",
"block_no": 6840368,
"epoch_no": 318,
"tx_count": "37",
"next_block": null,
"slot_leader": "pool19pyfv4xnln8x4l7auw0n0skk3hd97shun707hrw5d4s553ys74x",
"confirmations": 1,
"epoch_slot_no": 396018,
"previous_block": 6840367
"type": "block",
"object": "event",
"webhook_id": "98c7051ff06b4651949466655ef974fe",
"api_version": "1.0",
"create_date": 1643975112334,
"idempotency_key": "53a957187a4a4dd888b6839ea2d4452298c7051ff06b4651949466655ef974fe",
"network": "mainnet"

3. Transaction

This event is triggered every time a new transaction is added to the blockchain.
Payload example
"id": "123c4446-7a4f-4e8b-8baf-3c1437101859",
"data": {
"id": "3344667",
"fee": "305781",
"hash": "057585b42409a71c34d664e945acb92f30f09f966c5d18f098881c2dbf909d6f",
"size": 2825,
"block": {
"id": "3275904",
"fees": "1582516",
"hash": "00fd351c00be3f1775361de12576d51ee582157e330d1ebe596498295a46d02e",
"pool": {
"url": null,
"hash": null,
"raw_id": "7679567d0559ed3df7cb54a848b9568b04d1976b9926d54ae9efdd3f",
"pool_id": "pool1weu4vlg9t8knma7t2j5y3w2k3vzdr9mtnynd2jhfalwn76nwh48"
"size": 6980,
"time": "Wed Jan 19 2022 23:11:35 GMT+0000 (Coordinated Universal Time)",
"op_cert": "60ffa1e3c1ab6d03a5447d2f40ab023dbce45b13f0e372d63a964d31c7ee6079",
"out_sum": "18474206426",
"slot_no": 48264679,
"vrf_key": "vrf_vk1mzhz5k03lahvx0gdlqtplkyasgzn8w2cpf8y8a8f76nzskptzzhqdqyyq3",
"block_no": 3251329,
"epoch_no": 182,
"tx_count": "8",
"next_block": null,
"slot_leader": "pool1weu4vlg9t8knma7t2j5y3w2k3vzdr9mtnynd2jhfalwn76nwh48",
"confirmations": 1,
"epoch_slot_no": 10279,
"previous_block": 3251328
"deposit": "0",
"out_sum": "1591350310",
"block_id": "3275904",
"block_index": 1,
"script_size": 2014,
"invalid_before": "48264456",
"valid_contract": true,
"invalid_hereafter": "48278855"
"type": "transaction",
"object": "event",
"webhook_id": "5ef8985b5ee74b4388f324293df17173",
"api_version": "1.0",
"create_date": 1642633895460,
"idempotency_key": "5wIH/+H/cOj3K+gv3zOek89bEbIXDgxz5ef8985b5ee74b4388f324293df17173"

4. Epoch

Get notified when an epoch starts.
Payload example
"no": 178,
"start_time": "2022-01-04T20:20:24.000Z"

5. Delegation

This allows you to track delegations in the specified pool by its ticker or pool ID.
Payload Example
"id": "d0cf3218-761f-4ca1-900b-7750fb66fb59",
"data": {
"id": 97463,
"pool": {
"url": "https://apex.nextvm.net/test/testpoolMetadata.json",
"hash": "f5ac677b58443ed2c9c9d53aa56652e71a132679e67ed9068f0227867172faf4",
"name": "ApexTestPool",
"raw_id": "5f5ed4eb2ba354ab2ad7c8859f3dacf93564637a105e80c8d8a7dc3c",
"ticker": "APEXT",
"pool_id": "pool1ta0df6et5d22k2khezze70dvly6kgcm6zp0gpjxc5lwrce0seyq",
"homepage": "https://cardano-apexpool.github.io/test/",
"description": "Apex Cardano Test Pool"
"tx_id": 3340342,
"addr_id": 402710,
"slot_no": 48240615,
"redeemer_id": null,
"pool_hash_id": 1030,
"active_epoch_no": 183
"type": "delegation",
"object": "event",
"webhook_id": "7c827ccd2d524eb5aadf1e5a391077aa",
"api_version": "1.0",
"create_date": 1642609833343,
"idempotency_key": "p90C0LTvk1XX1Ha8+JDPzzFfybhxJYYt7c827ccd2d524eb5aadf1e5a391077aa"

6. Asset Activity

The Asset Activity Webhook allows you to track all the tokens with labels 721 (NFT) and 20 (FT). This provides your app with real-time changes when a new asset is minted on the blockchain, or is transferred between addresses.
Event object:
  • assets: Array of asset objects in the transaction.
    • quantity: Amount of assets minted or burned in the transaction. The quantity is 0 when the assets are part of the transaction (trasfered or not) but, but wasn't minted or burned.
    • ft_minted: The value istrue if a fungible token (FT) was minted in the transaction. In this case the transaction contains the creation of a token with the label 20 in the metadata. In Cardano FT are native tokens and part of the ledger.
    • policy_id: policy id of asset in the transaction. Cardano NFTs need to be identified by the policy id. This id is unique and attached permanently to the asset.
    • asset_name: asset name.
    • nft_minted: The value is true if a Non Fungible Token (NFT) was minted in the transaction. In this case the transaction contains the creation of a token with the label 721 in the metadata. In Cardano NFTs are native tokens and part of the ledger. For more information have a look at CIP25.
    • fingerprint: The CIP14 fingerprint for the multi-asset.
  • metadata: Asset metadata.
  • transaction: Transaction information.
Payload example
"id": "5a5810bc-388d-4ec3-adb3-578a3c2d2744",
"data": {
"assets": [
"quantity": 1,
"ft_minted": false,
"policy_id": "23b38042ebbe12754d51b29216474a159fe045183e6a31763fd2014b",
"asset_name": "token3",
"nft_minted": true,
"fingerprint": "asset1y3xu2rd0d7xk3d4phh5zy5gwxjuzcfccmwe79k"
"metadata": [
"json": {
"23b38042ebbe12754d51b29216474a159fe045183e6a31763fd2014b": {
"token3": {
"name": "token3",
"color": "black",
"image": "ipfs://QmVVQfgggha37KoCmRquDTdKzL1h5TKibAkKt7bsG1zykV",
"mouth": "open",
"left_eye": "round shaped",
"mediaType": "image/png",
"right_eye": "close set",
"collection": "Bulky",
"description": "description 3"
"label": "721"
"transaction": {
"id": "5264771",
"fee": "192913",
"hash": "a9aea41c9c779f5c0b1bfb88bb38421b2df378e816def0e4347e059abd2ee98f",
"size": 852,
"block": {
"id": "3790285",
"fees": "192913",
"hash": "44cb871bcca6ae919af790456d57322c333afbc70542afe9508b5b03b8c5d4c4",
"pool": {
"url": "https://ada4profit.com/testnet/JUNO.metadata.json",
"hash": "cb29a10b0b85a7befdadcdac55deba5651143bc5bc1b9016e4e9958905767cbc",
"name": "JUNO",
"ticker": "JUNO",
"pool_id": "pool15sfcpy4tps5073gmra0e6tm2dgtrn004yr437qmeh44sgjlg2ex",
"homepage": "https://junostakepool.com",
"size": 856,
"time": "Wed Aug 10 2022 02:57:20 GMT+0000 (Coordinated Universal Time)",
"op_cert": "ac3628ad64dbc80aaad05881edf521c899e9aa92c7754ad7334c263a196c7d42",
"out_sum": "9807087",
"slot_no": "65731024",
"vrf_key": "vrf_vk1deaac8se2ct0gvnmwck3zy8heantl2shwq7mvxzdvnl3vzq65kvqjgell6",
"block_no": 3769590,
"epoch_no": 222,
"tx_count": "1",
"next_block": null,
"slot_leader": "pool15sfcpy4tps5073gmra0e6tm2dgtrn004yr437qmeh44sgjlg2ex",
"confirmations": 1,
"epoch_slot_no": 196624,
"previous_block": 3769589
"deposit": "0",
"out_sum": "9807087",
"block_id": "3790285",
"block_index": 0,
"script_size": 0,
"invalid_before": "0",
"valid_contract": true,
"invalid_hereafter": "100000000"
"type": "asset",
"object": "event",
"webhook_id": "09068350e65946bfbedf5c2443c2a236",
"api_version": "1.0",
"create_date": 1660100241865,
"idempotency_key": "405c2c9981754a8d9eb1dadcbdc7d85809068350e65946bfbedf5c2443c2a236",
"network": "testnet"


This allows you to track when a sale is completed.
This webhooks is an internal Tangocrypto NFT API webhook and is triggered within our system, it is not related directly to events on the Cardano blockchain.
Payload Example
"id": "ec5ea629-25d1-4dd6-aedc-aae2a7e5bc5e",
"data": {
"saleId": "69c84acf0af6468aad42695109752691",
"tokens": [
"image": "ipfs://QmbR6vfPA9qrfpU5Zm81k5SMRdhE9yUVpCKPErGrbs8NcP",
"tx_id": "bcf3705786164225e2baf7634807d5e50c2f42c4c6ca0defe10e8a14294e5046",
"asset_name": "FACE#01"
"accountId": "ce0c3a8918284beab267c433d966385a",
"collectionId": "f5ee876c14a04c5092af43de09018c30"
"type": "nft",
"object": "event",
"webhook_id": "78ad7356e2d6472da14c5901b65691dd",
"api_version": "1.0",
"create_date": 1656261173901,
"idempotency_key": "3e61686a-2c05-4b08-8297-853ecb2b1cfb78ad7356e2d6472da14c5901b65691dd",
"network": "testnet"

Webhooks testing

Testing with webhook.site

Check out this video on how to create a webhook and test it:
Webhook testing tutorial with webhook.site
Or follow the written steps below:
There are many websites you can use to test out webhooks. For example, you can use https://webhook.site/ and copy your unique URL. Once you have the URL, you can test using the following steps:
  1. 1.
    Navigate to your Tangocrypto dashboard
  2. 2.
    Click "Create Webhook"
  3. 3.
    Fill the form with the unique URL generated in https://webhook.site/ and the address you want to monitor.
Create webhook in the dasboard
4. Now we send ADA to that address, here we are using ccvault to send 10 ADA from MyTestWallet1 to MyTestWallet2.
Transaction in Ccwallet
5. You should then see the result updated on website: https://webhook.site/
Payment webhook

Local testing with a Node.js Express app

Check out this video on how to create a webhook and test with a local Node.js express app:
Webhook testing with a Node.js express app
Or follow the written steps below:
If we want to test the webhook in our computer and we are behind a proxy/NAT device or a firewall we need a tool like Ngrok. Below you can see the diagram of how this will work. Tangocrypto Notify will trigger the webhook and make a POST to Ngrok cloud, then the request is forwarded to your local Ngrok client who in turn forwards it to the Node.js app listening on port 8000.
Node.js Express app architecture
Go to https://ngrok.com/ create a free account, download the binary and connect to your account. Create a Node.js app with Express and paste the following code to receive the webhook:
// This example uses Express to receive webhooks
const express = require('express');
const app = express();
app.post('/callback', express.json({type: 'application/json'}), (request, response) => {
const event = request.body;
// Handle the event
switch (event.type) {
case 'payment':
console.log("*** Payment event ***");
case 'epoch':
console.log("*** Epoch event ***");
// ... handle other event types
console.log(`Unhandled event type ${event.type}`);
// Return a response to acknowledge receipt of the event
response.json({received: true});
app.listen(8000, () => console.log('Running on port 8000'));
Run the app with the following command:
node app.js
The Express app will be listening on port 8000. To start an HTTP tunnel forwarding to your local port 8000 with Ngrok, run this next:
./ngrok http 8000
You should see something like this:
ngrok by @inconshreveable (Ctrl+C to quit)
Session Status online
Account Tangocrypto (Plan: Free)
Version 2.3.40
Region United States (us)
Web Interface
Forwarding http://80b4-2a02-c7f-f20b-6800-71cf-18c9-c6f1-dc5.ngrok.io -> http://localhost:3005
Forwarding https://80b4-2a02-c7f-f20b-6800-71cf-18c9-c6f1-dc5.ngrok.io -> http://localhost:3005
Connections ttl opn rt1 rt5 p50 p90
0 0 0.00 0.00 0.00 0.00
Copy the https Forwarding URL and append the /callback path and type the address you want to monitor.
If we transfer Ada to the address Tangocrypto Notify will detect the payment and the webhook will be triggered. Now we can receive the event on our local server. The response should be something like this:
*** Payment event ***
[ { network: 'testnet',
{ id: 1184244,
block_id: 3136969,
block_index: 0,
out_sum: 949155995,
fee: 168273,
deposit: 0,
size: 289,
invalid_before: null,
invalid_hereafter: 38824081,
valid_contract: true,
script_size: 0,
block: [Object] },
direction: 1,
{ address:
direction: 1,
index: 0,
value: '10000000' } } ]
You can also check the received event with the local Ngrok client http://localhost:4040/
local Ngrok inspector

Webhook Signature and Security

If you want to make your webhooks extra secure, you can verify that they originated from Tangocrypto by generating an HMAC SHA-256 hash code using your Authentication Token and request body.

1. Find your Authentication Token

Navigate to the top right corner of your Notify page to copy your "Auth Token".
Auth token

2. Validate the signature received

Every outbound request will contain a hashed authentication signature in the header which is computed by concatenating your auth token and request body then generating a hash using the HMAC SHA256 hash algorithm.
In order to verify this signature came from Tangocrypto, you simply have to generate the HMAC SHA256 hash and compare it with the signature received. This procedure is commonly known as verifying the digital signature.

Example Request Header

Content-Type: application/json;
x-tangocrypto-signature: your-hashed-signature

Example Signature Validation Function

const crypto = require('crypto'); //import the crypto module (not related to us :) )
function isValidSignature(request) {
const token = '<your token goes here>';
const headers = request.headers;
const signature = headers['x-tangocrypto-signature'];
const body = request.body;
const hmac = crypto.createHmac('sha256', token) // Create a HMAC SHA256 hash using the auth token
hmac.update(JSON.stringify(body), 'utf8') // Update the token hash with the request body using utf8
const digest = hmac.digest('hex');
return (signature === digest); // If signature equals your computed hash, return true
import hmac
import hashlib
import json
def isValidSignature(request):
token = '<your token goes here>';
headers = request['headers'];
signature = headers['x-tangocrypto-signature'];
body = request['body'];
string_body = json.dumps(body, separators=(',', ':'))
digest = hmac.new(
bytes(token, 'utf-8'),
msg=bytes(string_body, 'utf-8'),
return (signature == digest);