Webhooks
Callbacks and Main Terminology
Let's first define some main vocabulary related to callbacks that will make the process easier to talk about.
Callback
Shortly put, a “callback” is a POST request sent from Vaultody to a customer’s Callback URL when an event they have already subscribed for occurs.
Now with a bit more detail, a “callback” is used to describe an event reaction process where a user receives notifications on certain events after having first subscribed to them in the system. The term has become interchangeable with the word “webhook” which is used to describe the same thing. Callbacks can be very useful for customers who want to be notified about the state of transactions on the blockchain.
There are two sides to the callback - the server side and the customer side. The process involves customers subscribing to events on the server side. Once an event is completed, the server side notifies the customer side about it.
Callback
Definition
Specific processes are considered "events”. Simply put, an “event” is something that occurs on the Blockchain. Vaultody incorporates various endpoints, which hold different characteristics. You can see endpoints that represent events to which users can subscribe for a callback on our Dashboard.
Specific processes are considered "events”. Simply put, an “event” is something that occurs on the Blockchain. Vaultody incorporates various endpoints, which hold different characteristics.
Types
Different callback types exist, with a focus on the following:
Transaction request-
{
"walletId": "64463ff167ecf9000707b052",
"webhookId": "65b2633879435ab2f30727ff",
"idempotencyKey": "d14d2f41c0c2aef3f699ef2da79bc673ea5893eddf5966b7dde9fdde0e34b750",
"apiVersion": "2023-04-20",
"data": {
"event": "TRANSACTION_REQUEST",
"item": {
"blockchain": "tron",
"network": "nile",
"requestId": "65e89fd7e7684b8228dec633"
}
}
}
Transaction approved -
{
"walletId": "64463ff167ecf9000707b052",
"webhookId": "65b2633879435ab2f30727ff",
"idempotencyKey": "e5a10cfa989cf5b589ff1a6e7ca37ba1fe5307e26d41012e83cbf5db0a2fc31c",
"apiVersion": "2023-04-20",
"data": {
"event": "TRANSACTION_APPROVED",
"item": {
"blockchain": "tron",
"network": "nile",
"requestId": "65e89fd7e7684b8228dec633",
"requiredApprovals": 1,
"requiredRejections": 1,
"currentApprovals": 1,
"currentRejections": 0
}
}
}
Transaction rejected -
{
"walletId": "64463ff167ecf9000707b052",
"webhookId": "65b2633879435ab2f30727ff",
"idempotencyKey": "acb0ab432a693f604f45cfe08352b02074fa0053990f9e58140d41dfb71472db",
"apiVersion": "2023-04-20",
"data": {
"event": "TRANSACTION_REJECTED",
"item": {
"blockchain": "tron",
"network": "nile",
"requestId": "65e069a6e7684b8228ff583a",
"requiredApprovals": 1,
"requiredRejections": 1,
"currentApprovals": 0,
"currentRejections": 1
}
}
}
Incoming confirmed coin transaction -
{
"walletId": "64463ff167ecf9000707b052",
"webhookId": "65b2633879435ab2f30727ff",
"idempotencyKey": "c989505f082b3463a14d50f9ab785951366fd93012c9869ea1347d8f0b722ea7",
"apiVersion": "2023-04-20",
"data": {
"event": "INCOMING_CONFIRMED_COIN_TX",
"item": {
"blockchain": "binance-smart-chain",
"network": "testnet",
"address": "0x6746d7d3f59c1cf062ad25bff246660297122ff2",
"minedInBlock": {
"height": 37959320,
"hash": "0x8cff00dcce8c5eecd1bd42bb68b31ac126263cb819133a8e550465cf4426d711",
"timestamp": 1708588551
},
"currentConfirmations": 12,
"targetConfirmations": 12,
"amount": "0.3",
"unit": "BNB",
"transactionId": "0x94405584ed8c2177094f056e7ea6d2c157f3a3e2c1585669e222c5a40d104ef6"
}
}
}
Incoming confirmed token transaction -
{
"walletId": "64463ff167ecf9000707b052",
"webhookId": "65b2633879435ab2f30727ff",
"idempotencyKey": "b0c63d216889beb4f6d2154231f1becd2b7d9dbc349a6644cae839b5f35791b1",
"apiVersion": "2023-04-20",
"data": {
"event": "INCOMING_CONFIRMED_TOKEN_TX",
"item": {
"blockchain": "tron",
"network": "nile",
"address": "TDGFc6pDe5q2gc9zi4p2JQHfJTXVTBw7yu",
"minedInBlock": {
"height": 45323934,
"hash": "0000000002b3969e82281bb3b6d96e88e416d422faaae27a05b8522bc30c83fc",
"timestamp": 1710924012
},
"currentConfirmations": 4,
"targetConfirmations": 12,
"tokenType": "TRC-20",
"transactionId": "a6f8225d5a905fc236e5d85d88f77d127d48e80bb456042853c8ed6210182f4f",
"token": {
"tokenName": "Tether USD",
"tokenSymbol": "USDT",
"decimals": 6,
"tokensAmount": "50000",
"contract": "TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj"
}
}
}
}
Incoming confirmed internal transaction -
{
"walletId": "6577004524a7d60007f63383",
"webhookId": "65faf3fba2d408b0ffc9fe3d",
"idempotencyKey": "e1db8b89178b6041039d5d5b94022818370318d1a9d6efef281fa3c93c27044a",
"apiVersion": "2023-04-20",
"data": {
"event": "INCOMING_CONFIRMED_INTERNAL_TX",
"item": {
"blockchain": "binance-smart-chain",
"network": "testnet",
"address": "0x81318d093ca1a3a4d33970361f5937b1f3be2265",
"minedInBlock": {
"height": 38793161,
"hash": "0x1488f39e69d17cdade73cb3b110e9811c7e2a186afac7cdba3f33806913e40c5",
"timestamp": 1711091362
},
"currentConfirmations": 8,
"targetConfirmations": 12,
"amount": "0.000001",
"unit": "BNB",
"parentTransactionId": "0x354a445437b6795874273d9e578473fceb8e70049c42bf02f14103f4ae871211",
"operationId": "call"
}
}
}
Incoming Mined transaction -
{
"walletId": "64463ff167ecf9000707b052",
"webhookId": "65b2633879435ab2f30727ff",
"idempotencyKey": "4a159a6f79f0676d251e06eaee0491f7c20ad38987227c793482d4a4b2dbac18",
"apiVersion": "2023-04-20",
"data": {
"event": "INCOMING_MINED_TX",
"item": {
"blockchain": "tron",
"network": "nile",
"address": "TDGFc6pDe5q2gc9zi4p2JQHfJTXVTBw7yu",
"minedInBlock": {
"height": 45323934,
"hash": "0000000002b3969e82281bb3b6d96e88e416d422faaae27a05b8522bc30c83fc",
"timestamp": 1710924012
},
"direction": "incoming",
"currentConfirmations": 1,
"targetConfirmations": 12,
"tokenType": "TRC-20",
"transactionId": "a6f8225d5a905fc236e5d85d88f77d127d48e80bb456042853c8ed6210182f4f",
"token": {
"amount": "50000",
"contractAddress": "TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj",
"symbol": "USDT",
"name": "Tether USD",
"decimals": 6
}
}
}
}
Outgoing transaction failed -
{
"apiVersion": "2023-04-20",
"webhookId": "64dcca5c3c61607960b5e02d",
"idempotencyKey": "e14ab5620c3559334888f122eeef082dc999743f4e0373fedc29a0e2c3383d27",
"data": {
"event": "OUTGOING_FAILED",
"item": {
"blockchain": "xrp",
"currentApprovals": 1,
"currentRejections": 0,
"failedReason": "TO_ADDRESS_MUST_BE_DIFFERENT_FROM_SOURCE_ADDRESS",
"network": "testnet",
"requestId": "64dccace225244d7f76c2b96",
"requiredApprovals": 1,
"requiredRejections": 1
}
}
}
Outgoing transaction mined -
{
"walletId": "64463ff167ecf9000707b052",
"webhookId": "65b2633879435ab2f30727ff",
"idempotencyKey": "23237ea1af1689844e6b4779318a42f042aee630be5a4ebfac98b71521f2c578",
"apiVersion": "2023-04-20",
"data": {
"event": "OUTGOING_MINED",
"item": {
"blockchain": "tron",
"network": "nile",
"requestId": "65e89fd7e7684b8228dec633",
"requiredApprovals": 1,
"requiredRejections": 1,
"currentApprovals": 1,
"currentRejections": 0,
"transactionId": "f0509c32db54a723b2934f36964ad67f8ababd2944438c4c4c7ebd140b95f8de"
}
}
}
Outgoing transaction broadcasted -
{
"walletId": "64463ff167ecf9000707b052",
"webhookId": "65b2633879435ab2f30727ff",
"idempotencyKey": "4cd1ad4ca3e0698017d1e520c93a88142dd122acb975a1786a20f8818924455b",
"apiVersion": "2023-04-20",
"data": {
"event": "TRANSACTION_BROADCASTED",
"item": {
"blockchain": "tron",
"network": "nile",
"requestId": "65e89fd7e7684b8228dec633",
"requiredApprovals": 1,
"requiredRejections": 1,
"currentApprovals": 1,
"currentRejections": 0,
"transactionId": "f0509c32db54a723b2934f36964ad67f8ababd2944438c4c4c7ebd140b95f8de"
}
}
}
Callback URL
Callbacks require what is known as a ”Callback URL”. It is the destination where Vaultody sends the callback notification. Setting up this URL is done entirely on the customer’s side and is the first step to setting up a Callback.
Vaultody Callback basics
Vaultody Callbacks follow certain basics and the same standard that applies to all callback subscriptions.
- Callback requests sent from Vaultody are always POST;
- All Vaultody headers must by default incorporate the JSON content type application/json;
- All Vaultody Callbacks incorporate the use of a Callback URL, without exception.
- The attribute
x-signature
is a custom for Vaultody parameter, as indicated with thex-
in the beginning; - The structure of the Callback is always the same, no matter the subscription or type of event.
Idempotency and idempotentKey
Idempotency represents a process in computing and REST that a server uses to recognize 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 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 idempotencyKey
in the initial request, there is a guarantee that the specific action won’t be done more than once.
The idempotencyKey
is generated only on the server-end through the use of Vaultody Callbacks. It is added to the Callback request and is unique per initial callback request. idempotencyKey
values are generated randomly.
All original requests containing an idempotencyKey
, along with their body and response status code (both success and error), are saved by the system. After that, any subsequent requests containing the same idempotencyKey
will automatically return the same result.
Setting up callbacks
Callbacks combine certain elements and steps needed to create a correct reaction and on-time notification.
Their way of working requires the customer to first set up their Vault, and later on, whenever the subscription event occurs, the Vaultody will notify the customer. All steps are taken through the Vaultody Dashboard
- Customer sets up Callback
In the developers tab of the dashboard, you will see a Webhooks menu where you can add certain endpoints for which you would like to be notified.
- Customer subscribes for an event
Once you open the Add Endpoints module, you should select 4 main components. Endpoint URL where all notifications will be forwarded. Signed Secret key Selected Vaults that you would like to monitor. Select the events for which you want to be notified.
- Vaultody sends the Callback as a request
Vaultody's responsibility to the customer is to notify them according to their subscription for that particular event or events only when it/they occur. Whenever the event happens, Vaultody sends a request back to the customer that includes the data on the event, as requested by the customer. This is essentially the “callback” itself. If the callback is not confirmed by the customer side with a “200 OK” response, the system will follow a specific retry strategy.
It is also important to clarify that subscribing to an event and setting a callback for it does not necessarily mean that the callback will occur soon or even at all. We do everything possible to make sure that callbacks have no delays, errors, or issues, and are received as events occur.
Still, not receiving a callback for a subscribed event for a long time could be due to many reasons. There may be events that need to take days, weeks, or months to occur even just once, and some that may encounter problems, errors, and never happen at all.
Callback Retries
When the callback request is sent from Vaultody, it expects a response from the customer side as well.
Until a status code of “200 OK Request has been successful” is received, Vaultody will keep retrying the request. There will be three retries in total, distributed at certain intervals.
- Vaultody will send the requested callback when it’s time and a customer response is expected in the first 5 seconds;
- If a response isn’t received during that time frame the system is prepared to send in total 3 retries of the callback;
- The first callback retry will be sent after 30 minutes.
- After that, each following callback will take twice as long to be sent compared to the previous one. This is done with the purpose of extending the time during which the customer is going to receive the notification and its retries and providing an opportunity for the customer to send a “200 OK” response;
- Additionally, there’s an included “random value” into the algorithm as a factor of ”0.2”, which creates small but important fluctuations in the time frames between callback retries. This ensures that when large volumes of callbacks are sent, they are not sent in bulk due to having the same time frames, which could otherwise lead to crashes and server issues.
Callback Security
Vaultody has incorporated a security layer to keep callbacks from being intercepted, duplicated and to make sure customers are receiving their callbacks only from Vaultody.
Customer’s Signing Secret
The customer’s request can include data on the event they want to subscribe for and has the option to also have a Signing Secret
. This parameter is a required security measure for the benefit of the customer. The Signing Secret
can only be generated by the customer. It is used to create a unique hash string in the x-signature
response header in the callback request sent from Vaultody when the event occurs.
The Signing Secret
, and thus the x-signature
, are set up per event subscription, not per single callback. Hence, the customer only needs to set up the Signing Secret
once when creating the event. They can use a single Signing Secret
for all subscriptions, or different ones for each subscription. The security is effective enough even with just one common Signing Secret
.
Callback Signature
The x-signature
is provided in the header of the callback and represents a unique hash derived from all the information included in the callback about the event (which must be JSON encoded) and the customer’s Signing Secret
.
For this purpose, HMAC_SHA256 is used. It stands for Hash-based message authentication code, and along with its cryptographic hash function SHA256, it represents higher security than any other authentication code.
The cryptographic strength of the HMAC depends upon the size of the Signing Secret
that has been used. The most common attack against HMACs is brute force to uncover the secret key. HMACs are substantially less affected by collisions than their underlying hashing algorithms alone.
When the customer receives the callback request from Vaultody, they can first check the x-signature
before processing the request. By using their Signing Secret
, which only they would know, together with the information from the callback, they can generate a hash and compare it to the x-signature
hash that the Vaultody server has returned. They must match, which only then would authenticate Vaultody as the true sender.
After authentication of the callback, the customer can then proceed to processing it. If the authentication with the x-signature
fails, then the customer must ignore it as someone else is sending it.