Webhooks API

Overview

Webhooks are a powerful tool in Levain that enables you to receive real-time notifications for various events within your org account, including transaction updates and more. This guide will walk you through setting up your webhook endpoint to receive webhook events from Levain.

When should you use webhooks

As you integrate with Levain, you might want to be notified of certain events as they occur in your Levain org account, so that you can handle them accordingly.

Here are some common use cases for using webhooks:

  • Incoming deposits activity. You might want to be notified of incoming deposits on deposit addresses issued to your own users, so that you can credit the deposit within your internal ledger.
  • Outgoing withdrawals. You might also want to be notified of withdrawals initiated by your API co-signers, and that it has been confirmed on the blockchain, so that you can notify your own users.

Registering your webhook endpoint with Levain

To receive webhook events, you need to first register a webhook endpoint URL within Levain by going to Org settings, then Configure your webhook to add your webhook URL. Your webhook URL must be publicly available, use HTTPS and able to handle POST requests from Levain.

For now, you are able to register one webhook URL and your webhook endpoint will receive all webhook events sent by Levain.

Creating your webhook endpoint

We recommend your webhook endpoint to:

  • Verify the information received before processing
  • Handle retries if you fail to receive webhook events
  • Handle idempotent requests in the case of duplicate webhook events

Example webhook endpoint

We take security very seriously, and in future releases, all webhook events sent by Levain will be signed, and will contain a Levain-Signature in the request body. Your app will be able to verify the signature against the webhook event before processing, to confirm that the webhook event is genuine and comes from Levain, and not tampered with during transit.

Below is a Node.js with TypeScript code example of how you can design your webhook to check the event type for a wallet.deposit_address.incoming_deposit, to handle the event, and to return a HTTP 200 response.

In our example below, the webhook event is handled inline, though you might want to consider handling them asynchronously if there are complex or long-running processing.

Idempotency in APIs ensures that multiple identical requests have the same effect as a single request. This is vital for ensuring consistency and accuracy, particularly in error handling cases where requests might be duplicated, retried, or otherwise repeated. Levain expects your webhook event handlers to be idempotent.

import express, { Request, Response } from 'express';
import axios from 'axios';
import { verifySignature, creditUser } from './your-utils'; // Make sure to implement these utilities

const app = express();
const port = 3000;

app.use(express.json());

app.post('/levain-webhook-events', async (request: Request, response: Response) => {
  // Extract the idempotent key
  const idempotentKey = request.headers['Levain-Idempotent-Key'] as string;

  // Idempotency check
  if (yourIdempotencyCheck(idempotentKey)) {
    // Make sure to implement this check on your own
    response.status(200).end('Event already processed');
    return;
  }

  // Get deposit address and other details from request
  const { depositAddress, tokenBalance, tokenAddress, timestamp } = request.body.data;

  // Query Levain's GraphQL API for real-time balance
  const query = `
    query {
      address(address: "${depositAddress}") {
        balance
      }
    }
  `;

  const apiResponse = await axios.post('https://api.levain.com/graphql', {
    query: query,
  });

  // Process the data based on API response
  const balance = apiResponse.data.data.address.balance;
  // Additional logic to handle the event, such as crediting the user

  creditUser(depositAddress, balance); // Implement this function to handle crediting the user

  // Return HTTP 200 response
  response.status(200).end('Event processed successfully');
});

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});

Events

This section describes the webhook events that Levain sends and their associated data objects.

Wallet

wallet.incoming_deposit

This event is sent when an incoming deposit is detected on a wallet created through Levain. Currently, Levain will send the wallet.incoming_deposit to your webhook:

  • When we first detect the deposit on the blockchain network, in this case confirmed will be false
  • When we subsequently detect the deposit has reached the confirmation policy of the blockchain network, in this case confirmed will be true

Note that due to the nature of blockchain transactions for EVM-based blockchains, you may receive multiple transactions listings in transactions.native, transactions.internal, transactions.erc20 and transactions.trc20.

  • transactions.native will contain the native transactions associated with the deposit transaction, usually these will involve native tokens of a blockchain, if any.
  • transactions.internal will contain the internal transactions associated with the deposit transaction, usually these will involve incoming deposits sent from another contract (e.g. another multi-signature wallet, or using a decentralized exchange's swap and send to another address feature), if any.
  • transactions.erc20 will contain the ERC-20 transactions associated with the deposit transaction, only on EVM-compatible blockchains, if any.
  • transactions.trc20 will contain the TRC-20 transactions associated with the deposit transaction, only on Tron, if any.

We recommend you to rely on webhooks as a form of notifications, and to use the Levain GraphQL API to confirm the status of the deposit.

EVM-compatible chains
Parameter Data Type Description
confirmed Boolean Indicates whether the deposit is confirmed, based on the blockchain network's confirmation policy.
eventId String Unique identifier for the event, use this as an idempotent request.
orgId String Organization ID associated with the wallet receiving deposit.
chain String CAIP-2 blockchain identifier.
eventName String wallet.incoming_deposit
block.height Number The block height at which the deposit transaction was included.
block.hash String The hash of the block containing the deposit transaction.
block.timestamp Number The timestamp of the block containing the deposit transaction.
transactions.native Array of LevainWebhookTransactionData Array of native transactions associated with the deposit transaction (if any).
transactions.internal Array of LevainWebhookTransactionData Array of internal transactions (if any).
transactions.erc20 Array of LevainWebhookTransactionData Array of ERC-20 transactions, for EVM-compatible chains only (if any).
transactions.trc20 Array of LevainWebhookTransactionData Array of TRC-20 transactions, for Tron only (if any).
timestamp Number Unix timestamp when the event was generated.

The LevainWebhookTransactionData object contains the following fields:

Parameter Data Type Description
transactionHash String The transaction hash.
fromAddress String The address that sent the transaction.
toAddress String The address that received the transaction.
amount String The formatted amount of the transaction.
tokenSymbol String The token symbol of the transaction, if exists.
contractAddress String The contract address of the transaction, if exists.
rawAmount String The raw amount of the transaction, if exists.

Below is an example of a wallet.incoming_deposit webhook event of a 0.001 ETH native token deposit on Arbitrum Goerli Testnet.

ResponseJSON
{
  "confirmed": true,
  "eventId": "41ce55b9-7349-4a8e-90d4-5f3a2c65c225",
  "eventName": "wallet.incoming_deposit",
  "chain": "caip2:eip155:421613",
  "block": {
    "height": 44143340,
    "hash": "0xa0a5d6e37190a170e6c9d19488a8c59c1046ec04f79bc1150b04b7e6314aff66",
    "timestamp": 1695868685
  },
  "transactions": {
    "native": [
      {
        "transactionHash": "0x457ada6e2a2951f8cb37c16a206e73dd69ef55472a26648273d7a217a23cdac3",
        "fromAddress": "0x34dd9696556fb122222860746f97992dee396296",
        "toAddress": "0xa575c0d4850790a4f032a58c853b52d7e102b2ee",
        "amount": "0.001"
      }
    ],
    "internal": [],
    "erc20": []
  },
  "timestamp": 1695868727467,
  "orgId": "868202983161",
  "webhookId": "82e554a8-e470-4005-9592-b83cfb044c62"
}
Bitcoin
Parameter Data Type Description
confirmed Boolean Indicates whether the deposit is confirmed, based on the blockchain network's confirmation policy.
eventId String Unique identifier for the event, use this as an idempotent request.
orgId String Organization ID associated with the wallet receiving deposit.
chain String CAIP-2 blockchain identifier.
eventName String wallet.incoming_deposit
block.height Number The block height at which the deposit transaction was included.
block.hash String The hash of the block containing the deposit transaction.
block.timestamp Number The timestamp of the block containing the deposit transaction.
transactions Array of LevainWebhookTransactionData Array of native transactions associated with the deposit transaction (if any).
timestamp Number Unix timestamp when the event was generated.

The LevainWebhookTransactionData object contains the following fields:

Parameter Data Type Description
txId String The transaction id.
toAddress String The address that received the transaction.
amount String The formatted amount of the transaction.

Below is an example of a wallet.incoming_deposit webhook event of a 0.00005 BTC deposit on Bitcoin Testnet.

ResponseJSON
{
  "confirmed": false,
  "eventId": "67db34b6-aa9f-4802-a134-5bf8efa89049",
  "eventName": "wallet.incoming_deposit",
  "chain": "caip2:bip122:000000000933ea01ad0ee984209779ba",
  "block": {
    "height": 2579044,
    "hash": "0000000000000025c1be9805e4ceae180635f0961af6ae8f43da0d84623764b8",
    "timestamp": 1708580567
  },
  "transactions": [
    {
      "txId": "630e468f2184c627cd9022185e62f3424af371fdfec9041fe101fbf021adfd22",
      "toAddress": "tb1p445hu68tevly2lx27zanmygnx9l0fmf4lc6gd6uleat2dewh8j4sqw35wt",
      "amount": "0.00005"
    }
  ],
  "timestamp": 1708580614286,
  "orgId": "868202983161",
  "webhookId": "aa2b1870-6ac5-4d58-b296-538f1e0c642c"
}

Deposit Address

deposit_address.incoming_deposit

This event may be duplicated with wallet.incoming_deposit in certain scenarios, such as when a token is automatically flushed from a deposit address to the wallet's main address in a single transaction. In such a case, you will receive events across both wallet.incoming_deposit and deposit_address.incoming_deposit.

This event is sent when an incoming deposit is detected on a deposit address created through Levain. Currently, Levain will send the deposit_address.incoming_deposit event to your webhook:

  • When we first detect the deposit on the blockchain network, in this case confirmed will be false
  • When we subsequently detect the deposit has reached the confirmation policy of the blockchain network, in this case confirmed will be true

Note that due to the nature of blockchain transactions for EVM-based blockchains, you may receive multiple transactions listings in transactions.native, transactions.internal, transactions.erc20 and transactions.trc20.

  • transactions.native will contain the native transactions associated with the deposit transaction, usually these will involve native tokens of a blockchain, if any.
  • transactions.internal will contain the internal transactions associated with the deposit transaction, usually these will involve incoming deposits sent from another contract (e.g. another multi-signature wallet, or using a decentralized exchange's swap and send to another address feature), if any.
  • transactions.erc20 will contain the ERC-20 transactions associated with the deposit transaction, only on EVM-compatible blockchains, if any.
  • transactions.trc20 will contain the TRC-20 transactions associated with the deposit transaction, only on Tron, if any.

We recommend you to rely on webhooks as a form of notifications, and to use the Levain GraphQL API to confirm the status of the deposit.

EVM-compatible chains
Parameter Data Type Description
confirmed Boolean Indicates whether the deposit is confirmed, based on the blockchain network's confirmation policy.
eventId String Unique identifier for the event, use this as an idempotent request.
orgId String Organization ID associated with the wallet receiving deposit.
chain String CAIP-2 blockchain identifier.
eventName String deposit_address.incoming_deposit
block.height Number The block height at which the deposit transaction was included.
block.hash String The hash of the block containing the deposit transaction.
block.timestamp Number The timestamp of the block containing the deposit transaction.
transactions.native Array of LevainWebhookTransactionData Array of native transactions associated with the deposit transaction (if any).
transactions.internal Array of LevainWebhookTransactionData Array of internal transactions (if any).
transactions.erc20 Array of LevainWebhookTransactionData Array of ERC-20 transactions, for EVM-compatible chains only (if any).
transactions.trc20 Array of LevainWebhookTransactionData Array of TRC-20 transactions, for Tron only (if any).
timestamp Number Unix timestamp when the event was generated.

The LevainWebhookTransactionData object contains the following fields:

Parameter Data Type Description
transactionHash String The transaction hash.
fromAddress String The address that sent the transaction.
toAddress String The address that received the transaction.
amount String The formatted amount of the transaction.
tokenSymbol String The token symbol of the transaction, if exists.
contractAddress String The contract address of the transaction, if exists.
rawAmount String The raw amount of the transaction, if exists.

Below is an example of a deposit_address.incoming_deposit webhook event of a 0.001 ETH native token deposit on Arbitrum Goerli Testnet.

ResponseJSON
{
  "confirmed": true,
  "eventId": "41ce55b9-7349-4a8e-90d4-5f3a2c65c225",
  "eventName": "deposit_address.incoming_deposit",
  "chain": "caip2:eip155:421613",
  "block": {
    "height": 44143340,
    "hash": "0xa0a5d6e37190a170e6c9d19488a8c59c1046ec04f79bc1150b04b7e6314aff66",
    "timestamp": 1695868685
  },
  "transactions": {
    "native": [
      {
        "transactionHash": "0x457ada6e2a2951f8cb37c16a206e73dd69ef55472a26648273d7a217a23cdac3",
        "fromAddress": "0x34dd9696556fb122222860746f97992dee396296",
        "toAddress": "0xa575c0d4850790a4f032a58c853b52d7e102b2ee",
        "amount": "0.001"
      }
    ],
    "internal": [],
    "erc20": []
  },
  "timestamp": 1695868727467,
  "orgId": "868202983161",
  "webhookId": "82e554a8-e470-4005-9592-b83cfb044c62"
}
Bitcoin
Parameter Data Type Description
confirmed Boolean Indicates whether the deposit is confirmed, based on the blockchain network's confirmation policy.
eventId String Unique identifier for the event, use this as an idempotent request.
orgId String Organization ID associated with the wallet receiving deposit.
chain String CAIP-2 blockchain identifier.
eventName String deposit_address.incoming_deposit
block.height Number The block height at which the deposit transaction was included.
block.hash String The hash of the block containing the deposit transaction.
block.timestamp Number The timestamp of the block containing the deposit transaction.
transactions Array of LevainWebhookTransactionData Array of native transactions associated with the deposit transaction (if any).
timestamp Number Unix timestamp when the event was generated.

The LevainWebhookTransactionData object contains the following fields:

Parameter Data Type Description
txId String The transaction id.
toAddress String The address that received the transaction.
amount String The formatted amount of the transaction.

Below is an example of a deposit_address.incoming_deposit webhook event of a 0.00005 BTC deposit on Bitcoin Testnet.

ResponseJSON
{
  "confirmed": false,
  "eventId": "57c8ddc1-51ca-4336-8d6a-a6718af15a2e",
  "eventName": "deposit_address.incoming_deposit",
  "chain": "caip2:bip122:000000000933ea01ad0ee984209779ba",
  "block": {
    "height": 2579043,
    "hash": "000000000000000cef4a1d6264fe63f543128518a466d31c7e2a8d6395b52522",
    "timestamp": 1708580004
  },
  "transactions": [
    {
      "txId": "7a628a88e8f82bc4c593a4564c00070ea0a8cc7d402c894aa4ea6ea3358003bb",
      "toAddress": "tb1p9cwaluagw4nteug5fqrjauz8nlhd84vgzftzg44vpp7c59dy3cuq77wrx4",
      "amount": "0.00005"
    }
  ],
  "timestamp": 1708580034927,
  "orgId": "868202983161",
  "webhookId": "aa2b1870-6ac5-4d58-b296-538f1e0c642c"
}