Using Webhooks
Listen for events on the Cardano network and automatically trigger reactions.
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. 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.
Tangocrypto offers 4 different types of webhooks:
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
Callback requests sent from Tangocrypto are always POST and it contains a JSON payload;
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. 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"
}
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"
}
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"
}
Get notified when an epoch starts.
Payload example
{
"no": 178,
"start_time": "2022-01-04T20:20:24.000Z"
}
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"
}
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 istrue
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",
"description": "JUNO STAKE POOL ON TESTNET"
},
"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"
}
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.
- 2.Click "Create Webhook"
- 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

Payment webhook
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 ***");
console.log(event.data.payments);
break;
case 'epoch':
console.log("*** Epoch event ***");
break;
// ... handle other event types
default:
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 http://127.0.0.1:4040
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',
transaction:
{ id: 1184244,
hash:
'5130a99f0810bfb4feef5360c18a5e70f86bfcc15de25d6f5a56321a7ca14cbe',
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,
utxo:
{ address:
'addr_test1qpmslzfze9fjgqnwauxqpscc5fvgr7wvmymn5t505vd5httchgg3vgms2ur8uwlpku2hvznsu4dayk4fl3ggn0sr3lgqtmfvcr',
direction: 1,
hash:
'5130a99f0810bfb4feef5360c18a5e70f86bfcc15de25d6f5a56321a7ca14cbe',
index: 0,
value: '10000000' } } ]

local Ngrok inspector
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.
Navigate to the top right corner of your Notify page to copy your "Auth Token".

Auth token
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.
Content-Type: application/json;
x-tangocrypto-signature: your-hashed-signature
JavaScript
Python
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'),
digestmod=hashlib.sha256
).hexdigest()
return (signature == digest);
Last modified 9mo ago