# Layer 1 API Documentation
> Layer 1 documentation: digital assets, treasury, channels, webhooks, and approvals.
This file contains all documentation content in a single document following the llmstxt.org standard.
## Create a blockchain address
An **Address** is a unique identifier on a specific blockchain network where digital assets can be sent or received. It is generated using the public key from the key pair and functions similarly to an account number for blockchain transactions.
To create an Address for your asset pool:
1. Go to **Digital Assets** > **Addresses** and click **Create address**.
2. In the **Create Address** form, configure the address settings:
- **Creation method**. Select how to generate the address:
- **By network (single address)**. Creates a single address for the selected network.
- **By asset (multiple addresses)**. Creates multiple addresses associated with a specific asset.
- **Network** / **Asset**. Based on the chosen Creation method, select either
- Blockchain **Network** (Bitcoin, Tron, and so on), or
- **Asset** (DOGE, ETH).
- **Asset pool**. Select the earlier created [asset pool](/layer1/getting-started/getting-started-in-sandbox-portal/creating-an-asset-pool) to create these addresses under
- **Reference**. Provide a unique identifier for the address. You can enter this manually or click **Suggest** to auto-generate it.
- **Customer ID**. Optional. To associate this address with a specific customer, enter the corresponding Customer ID.
3. Click **Create address**. If you selected **By asset**, click **Create addresses**.
Once created, the address will appear in your **Addresses** list. Click on any address to view its details and balances.
For supported networks, Layer1 automatically provides addresses for all compatible assets. The **Supported assets** column shows them. In the example below, a single Solana address allows you to receive SOL and Solana tokens such as USDC, USDT, PYUSD, and USDG:
To explore Layer1's capabilities further, contact our Solutions team to request test funds and tokens.
Alternatively, learn how to [deposit and withdraw funds](/layer1/getting-started/getting-started-in-sandbox-portal/deposits-and-withdrawals) or follow the [integration testing guide](/layer1/use-cases/testing-your-integration) to simulate real-world workflows.
---
## Create a merchant account
:::warning
This page is only relevant for you if you plan to use Layer1's Payment Services—merchant-facing crypto checkout experience.
:::
To create an account for merchants, do the following:
1. Go to **Merchant Payments** > **Merchant accounts**.
2. Click **Add account** and in the **Add account** form, complete the following fields:
- **Company name**: Enter the legal name of the future merchant account.
- **Company registration number**: Optional. Enter the company's registration number.
- **Country of company registration**: Optional. Select the country where the company is registered.
- **Reference**: Enter or click **Suggest** to add the unique ID of the account to be used internally.
- **Network whitelist**: Enter IP addresses to restrict platform access for the admin user and the API key of this account. Otherwise, keep the default `0.0.0.0/0`.
- **Account owner details**: Enter the **First name**, **Last name**, and **Email address** of the admin user who will manage the account. The invitation message is sent to this email address, and this user has all merchant roles by default.
:::warning
If you’re exploring the account portal, use a different email address than the one you used for your tenant user.
Adding “+1” to your existing email alias is an easy way to do this, for example, `john.doe+1@example.com`.
:::
3. Click **Add account**. Check your email and complete the signup process by following instructions from the invitation message.
4. Go to the [Sandbox Account Portal](https://account.sandbox.layer1.com/).
## Add users
You can add users for your merchant in the Sandbox Account Portal. For that, go to **Settings** > **Users**. The flow is the same as [adding users to the tenant](/layer1/getting-started/getting-started-in-sandbox-portal/tenant-and-user-creation#add-users). Use the [Roles](/layer1/references/roles#account-roles) descriptions to provide appropriate access levels for users.

---
## Create an asset pool
An asset pool represents a collection of funds across different currencies and networks. By grouping these funds together, the complexity of managing multiple crypto addresses and key pairs is hidden from the customer, enabling them to focus on actions at the pool level rather than individual addresses. The asset pool simplifies fund management by abstracting away network-specific details, providing a unified interface for handling diverse crypto assets.
To create an asset pool:
1. Go to **Digital Assets** > **Asset pools** and click **Add asset pool**.
2. In the **Add asset pool** form, specify the following:
- **Name**. Enter the name for your new Asset Pool.
- **Reference**. Add the unique ID of the pool. You can do this manually or generate it by clicking **Suggest**.
3. Click **Add asset pool**.
The new Asset Pool appears in the list.
---
## Deposit and withdraw funds
Before continuing, refer to the [Test Blockchain Integration](/layer1/use-cases/testing-your-integration) guide to acquire test tokens.
## Deposit funds
Once you have sent funds from your connected wallet provider to the new address in Layer1, you can see the fine details of the deposit in **Digital Assets** > **Transaction history**. Select the asset pool, then click a transaction to open its details.
## Withdraw funds
To initiate a withdrawal operation to external wallet addresses, do the following:
1. Go to **Digital Assets** > **Requests**. If you have enough balance in your asset pool, you can request the transaction here.
2. Click **Send asset**. In **Select asset to send**, select the **Asset pool** with enough assets to fund the withdrawal, then select the asset and network you want to send.
3. In **Transaction Details**, check the **Sending from**, **Network**, and **Available balance** details, then complete the following fields:
- **Trusted address** or **One-time address**: Select **Trusted address** to send to a [contact's trusted address](/layer1/use-cases/digital-assets/add-a-contact), or **One-time address** to enter any address.
- **Recipient** or **Address**: Select the contact for a trusted address, or enter the destination crypto address for a one-time address.
- **Amount**: Enter the amount of cryptocurrency to be sent to the address.
- **Reference**: Enter or click **Suggest** to add the unique ID of the withdrawal for tracking purposes.
4. Click **Send**.
After requesting the transaction, go to **Digital Assets** > **Requests** to view the withdrawal as it transitions through various states to completion.
---
## Manage Smart Treasury
The Smart Treasury feature automates liquidity top-ups based on configurable thresholds and AI-powered forecasts derived from your treasury's historical data. This helps ensure your Layer1 Wallet always maintains sufficient balance to support trading and withdrawals — even during periods of volatility or high activity.
**Roles:** In order to use Smart Treasury your user must have the role `treasury:admin`. You can check and assign roles in **Manage account** > **Team members**.
Use the managed balance page to view top-ups and movements for a managed balance. To open it, go to **Smart treasury** and, on the **Balances** tab, click the managed balance you want to view. The page shows the available balance and the following sections:
- **Balance thresholds**: the **Minimum balance threshold**, **Target balance**, and **Warning balance threshold**. To change them or the balance name, click the edit icon. In **Edit managed balance**, update the values and click **Save**.
- **Top ups**: turn the toggle off to stop Smart Treasury from creating top-ups for this balance, and on to resume them. When you link a liquidity source to a managed balance, top-ups are enabled automatically.
- **Balance information**: the balance type and entity code, and for partner account balances, the partner account alias.
- **Liquidity sources**: the **Primary source**, **Secondary source**, and **Tertiary source** that fund top-ups. To change them, click the edit icon. The **Select liquidity sources** window opens. See [Add liquidity sources to a managed balance](#add-liquidity-sources-to-a-managed-balance).
- **Balance history (Last 8 Hours)**: a chart of the balance over the last eight hours, including withdrawals and top-up events. This chart is in beta.
- **Liquidity Movements**: the top-ups that Smart Treasury initiated for this balance, with their source, destination, amount, and status.
You can also manage liquidity sources from the **Balances** tab. Open the menu (**⋮**) next to a managed balance and click **Add liquidity sources**, or **Edit liquidity sources** if sources are already linked. To remove a managed balance, click **Delete** in the same menu.
### Add a managed balance
Managed balances can serve as either a destination for funds or a source of liquidity.
To add a managed balance, do the following:
1. On the Layer1 Portal, go to **Smart treasury** and click **+ Add Managed Balance**.
2. Select the balance type:
- **Asset pool**: a balance within your Layer1 Wallet
- **Venue**: a balance at a trading venue
- **Partner account**: a balance in a BVNK partner account. Available only if BVNK-integrated treasury is enabled for your tenant.
- **Paxos**: a balance held in a Paxos profile. Available only if Paxos venues are enabled for your tenant.
3. In **Name**, enter a name to identify the balance, for example `USDT (ETH) Operational funds`.
4. Complete the fields for the selected balance type:
- **Asset pool**: Select the existing asset pool.
- **Network**: Select the required crypto network.
- **Asset**: Select the cryptocurrency you want to use.
- **Alias**: Optional. A human-readable label used in venue withdrawals.
If the balance is unavailable for your selection, the form asks you to select a different asset, network, or asset pool.
If BVNK-integrated treasury is enabled for your tenant, the form shows **Partner account**, **Loan wallet ID**, and **System withdrawal sources** instead. See [BVNK wallet fields](#bvnk-wallet-fields).
- **Venue**: Select the trading venue.
- **Partner account**: Select the BVNK partner account that holds the balance.
- **Loan wallet ID**: Optional. See [BVNK wallet fields](#bvnk-wallet-fields).
- **System withdrawal sources**: Optional. See [BVNK wallet fields](#bvnk-wallet-fields).
If BVNK-integrated treasury is enabled for your tenant, the form shows **Venue**, **Partner account**, **Loan wallet ID**, and **System withdrawal sources** instead. See [BVNK wallet fields](#bvnk-wallet-fields).
- **Partner account**: Select the BVNK partner account that holds the balance.
- **Loan wallet ID**: Optional. See [BVNK wallet fields](#bvnk-wallet-fields).
- **System withdrawal sources**: Optional. See [BVNK wallet fields](#bvnk-wallet-fields).
- **Asset**: Select the asset held in the Paxos profile.
- **Paxos profile ID**: Enter the UUID of the Paxos profile that holds the balance. Required.
- **Alias**: Enter an alias for the balance. Required.
- **Monolith wallet**: The BVNK wallet linked to this venue. Required when this venue receives system withdrawals; leave blank otherwise. Enter the **Wallet ID**, **Loan wallet ID**, and **Wallet LSID**.
- **System withdrawal sources**: Optional. See [BVNK wallet fields](#bvnk-wallet-fields).
5. Specify the balance thresholds that trigger top-ups:
- **Minimum balance**: The balance that triggers a top-up when reached.
- **Target balance**: The balance you want to reach after a top-up.
- **Warning balance**: The balance that triggers a warning notification when reached.
6. Click **Add managed balance**. Your managed balance appears in the table.
#### BVNK wallet fields
These fields appear for **Partner account** and **Paxos** balances, and for **Asset pool** and **Venue** balances when BVNK-integrated treasury is enabled for your tenant. They link the managed balance to a wallet on the BVNK platform:
- **Partner account**: The BVNK partner account whose wallet holds the balance. Selecting it fills in the wallet details for you.
- **Wallet ID**: The numeric ID of the BVNK wallet. It must be greater than zero.
- **Loan wallet ID**: Optional. The numeric ID of the loan wallet associated with the BVNK wallet, if there is one.
- **Wallet LSID**: Optional. The ledger identifier of the BVNK wallet, for example `acc:22101919109000:wYzI5:0`.
- **System withdrawal sources**: Optional. Maps each managed balance to a remote wallet. When funds are withdrawn from this system, the mapped remote wallet (system pathway) ID is used. Click **Add source**, then select the **Managed balance** and enter the numeric **Remote wallet ID**.
### Add liquidity sources to a managed balance
Once you have created a managed balance, select the liquidity sources that fund its top-ups when the balance drops below the minimum.
To select liquidity sources, do the following:
1. On the table row item to which you want to link a liquidity source, click **Add liquidity sources**. The **Select liquidity sources** window opens.
2. In **Primary source**, select the managed balance that should fund top-ups first.
3. Optionally, in **Secondary source**, select a fallback. If your primary source cannot be used, funds are pulled from your secondary source so that your top-up rule still executes. To add a further fallback, such as a tertiary source, click the **+** icon; to remove one, click the trash icon.
4. If the balance you need isn't in the list, click [Add a managed balance](#add-a-managed-balance) to create it as a source.
5. Click **Confirm**.
When you link a source to a managed balance, the source will become **enabled** for top-ups and marked with a green tag in the Smart Treasury table.
---
## Create tenant account and add users
## Create account
To create your tenant account in Layer1 Sandbox and obtain access for your admin user:
1. Submit Company Information.
Provide the Layer1 Solutions team with the following details:
- **Company name**: This will be used as your tenant name in the system.
- **Admin user email address**: This user will be granted initial access and responsible for further setup.
2. **Tenant Creation**.
The Layer1 team will create your Sandbox tenant for you.
Once complete, an email will be sent to the provided admin email address from [no-reply@layer1.com](mailto:no-reply@layer1.com).
:::note
Check your spam or junk folder if you don’t see the email in your inbox.
:::
3. **Activate Admin Account**.
The admin user must follow the sign-up link included in the email. The link is valid for 3 days from the time of receipt.
Upon first login, the user will be prompted to:
- Set a password
- Set up multi-factor authentication (MFA) using one of the following methods:
- Passkey
- Authenticator code
4. **Access the Sandbox Portal**.
After completing the setup, the admin can access the Layer1 Sandbox via the following link: [https://app.sandbox.layer1.com/](https://app.sandbox.layer1.com/).
## Add users
To add users, do the following:
1. Log in to Sandbox Portal, and in the side menu, go to **Manage account** > **Team members**. Here you can see existing users and add new ones.
2. Click **Add User** and add the following details:
- **First name** and **Last name**
- **Email address**. User will receive a sign up link to this address.
- **Network whitelist**. Enter specific IP addresses in CIDR format to restrict access for the user. Otherwise, keep the default `0.0.0.0/0`.
- **Assign roles to user**. Select the roles to be assigned from the list. See the details in [Role descriptions](/layer1/references/roles).
:::note
You can only assign roles that you already have.
This security measure prevents users from granting higher privileges than they themselves hold.
:::
3. Click **Add user**.
The new user appears in the **Team members** list.
:::note
To resend an invitation, edit, or delete a user, open the menu (**⋮**) next to their name in the **Team members** list and click **Resend invite**, **Edit**, or **Delete**.
:::
---
## Organise wallets, channels, and payment links
:::warning
This page is only relevant for you if you plan to use Layer1's Payment Services—merchant-facing crypto checkout experience.
:::
After creating a merchant account, go to the Sandbox Account Portal to create wallets, channels, and payment links for your merchant.
## Create wallets
To create wallets for currencies, the merchant will hold on the platform, go to **Wallets** and click **+ Create a wallet**.
## Create channels
Channels are reusable blockchain wallets to which merchants' customers can deposit cryptocurrency. The cryptocurrency is credited and automatically converted into the wallets created earlier.
### View channel payments
To view deposits into a channel address, go to **Channels** > **Channel Payments**.
## View payment links
To create payment links, often referred to as invoice payments, use the [Create Payment](/l1-api/crypto-payments/payment-create) API endpoint.
To view payment links, on the Account Portal, go to **Payment Links**.
---
## Get started with sandbox portal
The Layer1 Sandbox Portal is your primary platform for testing and exploring the powerful capabilities of Layer1. Whether you are a Treasury Manager, Compliance Analyst, CTO, Product Manager, or simply curious about Layer1, our Sandbox provides a safe and isolated environment for experimenting with your use cases before deploying them to a live network.
## Why use the sandbox portal
We encourage you to become familiar with our API suite; however, the Sandbox Portal is an ideal starting point for your journey with Layer1. It offers:
- **Zero-Cost Development**: Experiment without any financial risk.
- **Realistic Testing**: Validate functionality in an environment that replicates the production setting.
- **Hands-On Learning**: Get acquainted with Layer1’s development tools, APIs, and best practices.
We recommend refreshing your understanding of [Layer1's Concepts](/layer1/getting-started/#conceptual-model) as you go along.
:::warning[Trading / Conversions]
The Trading / Conversions functionality is not configured by default in the Sandbox. Contact our Solutions team for more information and to schedule a demo.
:::
---
## Overview
Layer1 is BVNK's self-managed payments product: a self-hosted digital asset platform built for modern payment and treasury infrastructure. Unlike BVNK Payments, where BVNK operates the infrastructure on your behalf, Layer1 runs entirely within your own environment — so you hold the keys, control custody, and own the fund flows end to end. It abstracts away the complexity of interacting with multiple blockchains, letting you secure keys, process transactions, and manage assets from a single platform.
Designed by veterans of payment platforms, Layer1 helps you integrate faster and launch faster, with robust tooling and modular components for custody, trading, compliance, and more.
With Layer1, you can:
- Enable 24/7, real-time payments across multiple blockchain networks.
- Support 10+ major cryptocurrencies out of the box.
- Build tailored digital asset workflows for your business.
- Customize custody models based on your risk profile.
- Manage your own crypto treasury.
- Launch revenue-sharing models with minimal engineering effort.
## Why teams choose Layer1
- Minimal blockchain expertise required. Integrate without needing to know chains, gas, or token standards.
- Automated fund flows. Deposits are automatically routed and consolidated.
- Plug-and-play compliance. Just connect your provider—no extra dev work.
- No ongoing engineering burden. Layer1 is maintained for you; your team stays lean.
- Smart treasury management. Trade and move assets across venues easily.
- Simple asset conversion. Use a single API to convert between supported assets — including synthetic pairs.
## Layer1 core systems
Your Layer1 account gives you access to a suite of composable services via a single, unified API:
- **Layer1 Vault**.
A self-hosted key management system (KMS) that secures private keys inside your infrastructure. Built with best-in-class security standards for digital asset custody.
- **Digital Asset Services**.
A blockchain abstraction layer that simplifies cross-chain operations. Automatically consolidates funds, routes withdrawals, and calculates optimal fees across networks and tokens.
- **Trading Service**.
Route and execute trades across multiple venues through one integration. The Layer1 trade router finds the best price and execution path — including synthetic pairs—without manual strategy setup.
- **Transaction Screening**.
Plug in your compliance provider credentials to enable automatic transaction screening. Manage flagged transactions through an integrated dashboard — no extra development needed.
- **User Interface**.
A clean, white-labeled interface that gives your team (or customers) access to all Layer1 features without writing code.
## Conceptual model
The Layer1 platform is a multi-tenant solution. A single tenant can create as many distinct asset pools as required.
Four main components function within Layer1 creation:
- **Asset Pool:**
An asset pool represents a collection of funds across different currencies and networks. By grouping these funds together, the complexity of managing multiple crypto addresses and key pairs is hidden from the customer, enabling them to focus on actions at the pool level rather than individual addresses. The asset pool simplifies fund management by abstracting away network-specific details, providing a unified interface for handling diverse crypto assets.
- **KeyPair:**
A key pair is the cryptographic foundation of blockchain transactions. It consists of a public and a private keys. The key pair secures access to blockchain addresses and ensures that only authorised transactions can be conducted. Public-private key cryptography is integral to verifying transactions and securing assets on the blockchain.
Multiple key pairs can be created within a single asset pool. A key pair is used to create an address on any of the supported blockchain networks.
A single key pair can be used to create a unique blockchain address on any of the supported networks. Each network address must be distinct and cannot be duplicated. For instance, a single key pair cannot generate multiple Ethereum addresses. However, the same key pair can be used to create different blockchain addresses on various networks such as Ethereum, Bitcoin, Tron, etc.
- **Address:**
An address is a unique identifier on a specific blockchain network where assets can be sent or received. It is generated using the public key from the key pair and serves as the "account number" for blockchain transactions.
When the blockchain network allows it, a single address can support multiple assets. For example, a single Ethereum address can hold ETH, USDT, and USDC assets.
- **Asset:**
An asset is the value held in a specific currency that can be transferred on the blockchain. Assets are units that move between addresses and are accounted for within an asset pool.
The diagram illustrates the relationships between these four components:

---
## Auto-consolidate assets in master wallet
Auto-consolidation is a native feature of the Layer1 platform that automatically aggregates dispersed liquidity from multiple blockchain deposit wallets into a designated **master address per blockchain network**.
This ensures that your funds remain accessible for withdrawal and payment processing, thereby eliminating the operational risks associated with stranded or fragmented balances. Over time, without this consolidation, incoming deposits would accumulate across numerous wallet addresses, resulting in inefficient and fragmented access to funds.
Auto-consolidation unlocks your operational liquidity, centralising it and making it ready for supercharged digital asset payments.
This feature is built into Layer1, and **requires no custom wallet orchestration** from your side. It works out of the box using carefully designed parameters optimised for the economics and network characteristics of each blockchain.
There’s no code to write, no infrastructure to maintain—integrate your system into Layer1 and start consolidating.
***
## Automatic creation of a master wallet
The successfully deposited funds undergo an auto-consolidation phase. That means they are transferred from the deposit address to a central pooled wallet, ensuring they aren't left spread across multiple wallets. If this is the first deposit of this cryptocurrency type to the asset pool, a master wallet is automatically created.
The following webhook of the `layer1:crypto:address:created` event type is initially sent once the deposit has been updated to `SUCCESS`:
```json title="Address Created"
{
"event": "layer1:crypto:address:created",
"timestamp": "2024-06-05T20:40:31.443861137Z",
"data": {
"id": "8e1fcdb5-8edc-49b7-9289-1c41c5bcd818",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d",
"network": "ETHEREUM",
"keyPairId": "fdc9f5a0-698c-4163-ab21-df85f343a2a4",
"reference": "AssetPoolTwo",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
}
}
```
From now on, the funds will be swept into the master address `0x96073c4b415d60ade18de32e43efc2914731e03d`.
## Automated gas fee funding
Because consolidation is an on-chain operation, it does incur gas fees. To simplify this:
- Gas fees are **automatically paid** using native token funds from your master address.
- You don't need to pre-fund each deposit wallet or manage gas provisioning yourself.
- The system handles the timing and funding logic to ensure smooth execution.
:::note
**Please note:** This gas fee funding can sometimes include an additional small fee to activate the wallet before consolidation can happen. For example on TRON we may send 1.100001 TRX. The activation cost (1.1 TRX) plus an actual transaction along with it (like a very small 0.000001 TRX amount).
:::
Based on years of experience processing low-value, high-frequency payments with BVNK, we’ve developed various **gas fee optimisation strategies** tailored to the tokenomics of each network, such as:
- Minimum value threshold
- Batch consolidations
- [Staking](/layer1/use-cases/digital-assets/staking) (TRON network)
- And more
Contact the Solutions team for more information and features.
## Consolidation minimum thresholds
In order to ensure that consolidation occurs at the optimal price and interval, gas fee strategies and minimum balance thresholds are configured per asset and network to ensure efficiency. Contact the Solutions team for more information.
***
## Manual consolidation
While our automated consolidation logic is designed to optimise liquidity centralisation and on-chain cost efficiency, there may be operational cases where you need immediate control.
With the **Manual Consolidation** feature in the **Layer1 Portal**, you can initiate fund consolidation on demand, bypassing scheduled or threshold-based logic.
Use this to:
- Quickly prepare funds for an urgent withdrawal.
- Consolidate a specific set of wallets regardless of network economics.
- Manage a network where your desired operational thresholds differ from the default settings.
To consolidate specific assets:
1. In Layer1 Portal, go to **Digital Assets** > **Asset pools** and click **Consolidate liquidity**.
2. In the **Consolidate liquidity** form, complete the following fields:
- **Asset pool**: Select the asset pool to consolidate into a single collection of assets.
- **Network**: Select the blockchain or platform where your distributed funds are currently held.
- **Asset**: Select the particular cryptocurrency or token you want to consolidate on the selected network.
- **Minimum wallet balance**: Set a threshold for including wallets in the consolidation process. For example, consolidate only addresses with a balance \>0.1 of your chosen asset. This allows you to optimise the operation by excluding low-value addresses where consolidation costs outweigh benefits.
- **Maximum number of addresses**: Set the maximum number of wallets to consolidate. This feature enables you to manage the process in smaller, more controllable batches, allowing for review and adjustment before proceeding with larger consolidations. This gives you control to consolidate economically vs. the gas fee.
3. Click **Consolidate**.
***
## Webhooks
### "Balance updated" webhooks
During the auto-consolidation phase, the balance is updated for both the deposit address and the new master address. The following "balance updated" webhooks are received during consolidation:
- "**Balance Update: Funds Reserved**" shows the amount actioned in the consolidation move from `AVAILABLE` to `RESERVED` in the original deposit address.
- "**Balance Update: New Funds Available**" shows that the funds have been received by the new master address as shown in the `AVAILABLE` section. Note it is slightly less due to network fees.
- "**Balance Update: Funds Released**" shows the amount released from the `RESERVED` section and the `BLOCKCHAIN` section after the successful consolidation. Note the small leftover funds or 'dust'.
```json title="Example webhook"
{
"event": "layer1:digital:balance:updated",
"timestamp": "2024-06-05T20:40:31.641988100Z",
"data": {
"reference": "First Eth Address",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9",
"asset": "ETH",
"funds": [
{
"type": "AVAILABLE",
"balance": "0.00",
"delta": "-0.20"
},
{
"type": "BLOCKCHAIN",
"balance": "0.20",
"delta": "0.00"
},
{
"type": "RESERVED",
"balance": "0.20",
"delta": "0.20"
}
]
}
}
```
```json title="Example webhook"
{
"event": "layer1:digital:balance:updated",
"timestamp": "2024-06-05T20:41:27.349393331Z",
"data": {
"reference": "AssetPoolTwo",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d",
"asset": "ETH",
"funds": [
{
"type": "AVAILABLE",
"balance": "0.199630313934810125",
"delta": "0.199630313934810125"
},
{
"type": "BLOCKCHAIN",
"balance": "0.199630313934810125",
"delta": "0.199630313934810125"
},
{
"type": "RESERVED",
"balance": "0.00",
"delta": "0.00"
}
]
}
}
```
```json title="Example webhook"
{
"event": "layer1:digital:balance:updated",
"timestamp": "2024-06-05T20:41:27.341091297Z",
"data": {
"reference": "First Eth Address",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9",
"asset": "ETH",
"funds": [
{
"type": "AVAILABLE",
"balance": "0.000058795984828875",
"delta": "0.000058795984828875"
},
{
"type": "BLOCKCHAIN",
"balance": "0.000058795984828875",
"delta": "-0.199941204015171125"
},
{
"type": "RESERVED",
"balance": "0.00",
"delta": "-0.20"
}
]
}
}
```
### "Consolidation status change" webhooks
These webhooks are sent out to inform you about the current status of the consolidation of funds from one address you own to another address in the same asset pool. This means there could be duplicates of the same type of webhook but from different addresses, so pay special attention to these in the webhook. There are seven or ten update webhooks expected during the consolidation phase:
1. `CREATED`
2. `FUNDS_RESERVED`
3. `SIGNED`
4. `DETECTED`: two webhooks, one for each address
5. `UNCONFIRMED`: two webhooks, one for each address
6. `CONFIRMED`: two webhooks, one for each address
7. `SUCCESS`
```json title="Example webhook"
{
"event": "layer1:crypto:consolidation:status-change",
"timestamp": "2024-06-05T20:40:31.722661928Z",
"data": {
"id": "018fea21bff771e791d51a34af5c59b7d18a869f191944a09e5e784d7ca8f45a69",
"type": "CONSOLIDATION",
"asset": "ETH",
"amount": "0.20",
"status": "CREATED",
"address": {
"id": "d18a869f-1919-44a0-9e5e-784d7ca8f45a",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9",
"network": "ETHEREUM",
"keyPairId": "44cf54df-93e9-4dd9-84c9-e96f0cf90f3e",
"reference": "First Eth Address",
"customerId": "customerOne",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
},
"sources": [
{
"amount": "0.20",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9"
}
],
"createdAt": "2024-06-05T20:40:31.479Z",
"reference": "consolidation-ETH-018fea20-a69e-7cc5-8066-f8d03362e797",
"updatedAt": "2024-06-05T20:40:31.692479Z",
"destinations": [
{
"amount": "0.20",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d"
}
]
}
}
```
```json title="Example webhook"
{
"event": "layer1:crypto:consolidation:status-change",
"timestamp": "2024-06-05T20:40:31.872516799Z",
"data": {
"id": "018fea21bff771e791d51a34af5c59b7d18a869f191944a09e5e784d7ca8f45a69",
"type": "CONSOLIDATION",
"asset": "ETH",
"amount": "0.20",
"status": "FUNDS_RESERVED",
"address": {
"id": "d18a869f-1919-44a0-9e5e-784d7ca8f45a",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9",
"network": "ETHEREUM",
"keyPairId": "44cf54df-93e9-4dd9-84c9-e96f0cf90f3e",
"reference": "First Eth Address",
"customerId": "customerOne",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
},
"sources": [
{
"amount": "0.20",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9"
}
],
"createdAt": "2024-06-05T20:40:31.479Z",
"reference": "consolidation-ETH-018fea20-a69e-7cc5-8066-f8d03362e797",
"updatedAt": "2024-06-05T20:40:31.849128Z",
"destinations": [
{
"amount": "0.20",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d"
}
]
}
}
```
```json title="Example webhook"
{
"event": "layer1:crypto:consolidation:status-change",
"timestamp": "2024-06-05T20:40:32.408072441Z",
"data": {
"id": "018fea21bff771e791d51a34af5c59b7d18a869f191944a09e5e784d7ca8f45a69",
"type": "CONSOLIDATION",
"asset": "ETH",
"amount": "0.20",
"status": "SIGNED",
"address": {
"id": "d18a869f-1919-44a0-9e5e-784d7ca8f45a",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9",
"network": "ETHEREUM",
"keyPairId": "44cf54df-93e9-4dd9-84c9-e96f0cf90f3e",
"reference": "First Eth Address",
"customerId": "customerOne",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
},
"sources": [
{
"amount": "0.20",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9"
}
],
"createdAt": "2024-06-05T20:40:31.479Z",
"reference": "consolidation-ETH-018fea20-a69e-7cc5-8066-f8d03362e797",
"updatedAt": "2024-06-05T20:40:32.385590Z",
"destinations": [
{
"amount": "0.20",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d"
}
]
}
}
```
```json title="Example webhook"
{
"event": "layer1:crypto:consolidation:status-change",
"timestamp": "2024-06-05T20:40:34.593627067Z",
"data": {
"id": "018fea21bff771e791d51a34af5c59b78e1fcdb58edc49b792891c41c5bcd8186f",
"hash": "0xc12e1f147b72e697469e24a5caadac1f4cf938cb7d40187eef3612746eb6b53e",
"type": "CONSOLIDATION",
"asset": "ETH",
"amount": "0.199630313934810125",
"status": "DETECTED",
"address": {
"id": "8e1fcdb5-8edc-49b7-9289-1c41c5bcd818",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d",
"network": "ETHEREUM",
"keyPairId": "fdc9f5a0-698c-4163-ab21-df85f343a2a4",
"reference": "AssetPoolTwo",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
},
"sources": [
{
"amount": "0.199630313934810125",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9"
}
],
"createdAt": "2024-06-05T20:40:31.479Z",
"reference": "consolidation-ETH-018fea20-a69e-7cc5-8066-f8d03362e797",
"updatedAt": "2024-06-05T20:40:34.531163Z",
"destinations": [
{
"amount": "0.199630313934810125",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d"
}
]
}
}
```
```json title="Example webhook"
{
"event": "layer1:crypto:consolidation:status-change",
"timestamp": "2024-06-05T20:40:37.022040178Z",
"data": {
"id": "018fea21bff771e791d51a34af5c59b78e1fcdb58edc49b792891c41c5bcd8186f",
"hash": "0xc12e1f147b72e697469e24a5caadac1f4cf938cb7d40187eef3612746eb6b53e",
"type": "CONSOLIDATION",
"asset": "ETH",
"amount": "0.199630313934810125",
"status": "UNCONFIRMED",
"address": {
"id": "8e1fcdb5-8edc-49b7-9289-1c41c5bcd818",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d",
"network": "ETHEREUM",
"keyPairId": "fdc9f5a0-698c-4163-ab21-df85f343a2a4",
"reference": "AssetPoolTwo",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
},
"sources": [
{
"amount": "0.199941204015171125",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9"
}
],
"createdAt": "2024-06-05T20:40:31.479Z",
"reference": "consolidation-ETH-018fea20-a69e-7cc5-8066-f8d03362e797",
"updatedAt": "2024-06-05T20:40:36.987028Z",
"destinations": [
{
"amount": "0.199630313934810125",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d"
}
],
"networkDetail": {
"fee": {
"asset": "ETH",
"amount": "0.000310890080361"
},
"block": {
"number": 6046670,
"timestamp": "2024-06-05T20:40:36Z"
}
}
}
}
```
```json title="Example webhook"
{
"event": "layer1:crypto:consolidation:status-change",
"timestamp": "2024-06-05T20:41:27.295882772Z",
"data": {
"id": "018fea21bff771e791d51a34af5c59b78e1fcdb58edc49b792891c41c5bcd8186f",
"hash": "0xc12e1f147b72e697469e24a5caadac1f4cf938cb7d40187eef3612746eb6b53e",
"type": "CONSOLIDATION",
"asset": "ETH",
"amount": "0.199630313934810125",
"status": "CONFIRMED",
"address": {
"id": "8e1fcdb5-8edc-49b7-9289-1c41c5bcd818",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d",
"network": "ETHEREUM",
"keyPairId": "fdc9f5a0-698c-4163-ab21-df85f343a2a4",
"reference": "AssetPoolTwo",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
},
"sources": [
{
"amount": "0.199941204015171125",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9"
}
],
"createdAt": "2024-06-05T20:40:31.479Z",
"reference": "consolidation-ETH-018fea20-a69e-7cc5-8066-f8d03362e797",
"updatedAt": "2024-06-05T20:41:27.268151Z",
"destinations": [
{
"amount": "0.199630313934810125",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d"
}
],
"networkDetail": {
"fee": {
"asset": "ETH",
"amount": "0.000310890080361"
},
"block": {
"number": 6046670,
"timestamp": "2024-06-05T20:40:36Z"
}
}
}
}
```
```json title="Example webhook"
{
"event": "layer1:crypto:consolidation:status-change",
"timestamp": "2024-06-05T20:41:27.398050688Z",
"data": {
"id": "018fea21bff771e791d51a34af5c59b7d18a869f191944a09e5e784d7ca8f45a69",
"hash": "0xc12e1f147b72e697469e24a5caadac1f4cf938cb7d40187eef3612746eb6b53e",
"type": "CONSOLIDATION",
"asset": "ETH",
"amount": "0.199941204015171125",
"status": "SUCCESS",
"address": {
"id": "d18a869f-1919-44a0-9e5e-784d7ca8f45a",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9",
"network": "ETHEREUM",
"keyPairId": "44cf54df-93e9-4dd9-84c9-e96f0cf90f3e",
"reference": "First Eth Address",
"customerId": "customerOne",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
},
"sources": [
{
"amount": "0.199941204015171125",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9"
}
],
"createdAt": "2024-06-05T20:40:31.479Z",
"reference": "consolidation-ETH-018fea20-a69e-7cc5-8066-f8d03362e797",
"updatedAt": "2024-06-05T20:41:27.375074Z",
"destinations": [
{
"amount": "0.199630313934810125",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d"
}
],
"networkDetail": {
"fee": {
"asset": "ETH",
"amount": "0.000310890080361"
},
"block": {
"number": 6046670,
"timestamp": "2024-06-05T20:40:36Z"
}
}
}
}
```
At the final point of getting the `SUCCESS` webhook, the deposit has been successfully received by the asset pool. This triggers an automatic consolidation round, which transfers the funds to a central master wallet that pools all the funds for future use.
Now that the central wallet address exists, the consolidation can be triggered, which will send out a series of consolidation webhooks, which show the funds moving from the deposit address, `0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9`, to the new central wallet under the `destinations` array, `0x96073c4b415d60ade18de32e43efc2914731e03d`.
```json title="Consolidation: Status Change"
{
"event": "layer1:crypto:consolidation:status-change",
"timestamp": "2024-06-05T20:40:31.722661928Z",
"data": {
"id": "018fea21bff771e791d51a34af5c59b7d18a869f191944a09e5e784d7ca8f45a69",
"type": "CONSOLIDATION",
"asset": "ETH",
"amount": "0.20",
"status": "CREATED",
"address": {
"id": "d18a869f-1919-44a0-9e5e-784d7ca8f45a",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9",
"network": "ETHEREUM",
"keyPairId": "44cf54df-93e9-4dd9-84c9-e96f0cf90f3e",
"reference": "First Eth Address",
"customerId": "customerOne",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
},
"sources": [
{
"amount": "0.20",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9"
}
],
"createdAt": "2024-06-05T20:40:31.479Z",
"reference": "consolidation-ETH-018fea20-a69e-7cc5-8066-f8d03362e797",
"updatedAt": "2024-06-05T20:40:31.692479Z",
"destinations": [
{
"amount": "0.20",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d"
}
]
}
}
```
Further webhooks will be sent, showing the funds have been moved from the source address to the destination address.
```json title="Balance Updated"
{
"event": "layer1:digital:balance:updated",
"timestamp": "2024-06-05T20:41:27.341091297Z",
"data": {
"reference": "First Eth Address",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9",
"asset": "ETH",
"funds": [
{
"type": "AVAILABLE",
"balance": "0.000058795984828875",
"delta": "0.000058795984828875"
},
{
"type": "BLOCKCHAIN",
"balance": "0.000058795984828875",
"delta": "-0.199941204015171125"
},
{
"type": "RESERVED",
"balance": "0.00",
"delta": "-0.20"
}
]
}
}
```
```json title="Another Balance Updated"
{
"event": "layer1:digital:balance:updated",
"timestamp": "2024-06-05T20:41:27.349393331Z",
"data": {
"reference": "AssetPoolTwo",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d",
"asset": "ETH",
"funds": [
{
"type": "AVAILABLE",
"balance": "0.199630313934810125",
"delta": "0.199630313934810125"
},
{
"type": "BLOCKCHAIN",
"balance": "0.199630313934810125",
"delta": "0.199630313934810125"
},
{
"type": "RESERVED",
"balance": "0.00",
"delta": "0.00"
}
]
}
}
```
---
## Create a crypto address
Once you have an Asset Pool and `id` from the earlier request, you can generate crypto addresses within that Asset Pool. See the list of available cryptocurrency addresses in the [Supported Assets and Networks](/layer1/references/supported-assets-and-protocols) guide.
To create a Crypto Address, send the [`POST /digital/v1/addresses`](/l1-api/digital-assets/create-address) request with the following body parameters. Use the `id` from the previous request as `assetPoolId`.
```json title="Example request"
{
"network": "ETHEREUM",
"asset": "ETH",
"reference": "First Eth Address",
"customerId": "customerOne",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
}
```
Upon successfully submitting the request, you'll receive details of the newly created Address:
```json title="Example response"
{
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600",
"keyPairId": "44cf54df-93e9-4dd9-84c9-e96f0cf90f3e",
"reference": "First Eth Address"
}
```
At this point, the address is created on the relevant blockchain and associated with `AssetPoolId` from the request. Then it is delivered asynchronously through a webhook to the destination set up earlier.
## Address Created Webhook
The "address created" webhook is sent to any destination subscribed to the `layer1:crypto:address:created` event:
```json title="Address Created"
{
"event": "layer1:crypto:address:created",
"timestamp": "2024-05-30T20:03:54.075299611Z",
"data": {
"id": "d18a869f-1919-44a0-9e5e-784d7ca8f45a",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9",
"network": "ETHEREUM",
"keyPairId": "44cf54df-93e9-4dd9-84c9-e96f0cf90f3e",
"reference": "First Eth Address",
"customerId": "customerOne",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
}
}
```
You can extract the address from `data.address` and show it to users, from which you expect deposits to be sent, and who can pay in the correct cryptocurrency.
Share the Address with a depositor and wait for a deposit to be transferred. For the next steps, see [Receive deposit and ingest the webhooks](/layer1/use-cases/digital-assets/accepting-deposits-to-new-addresses/receive-deposit-and-ingest-the-webhooks).
---
## Create an asset pool(Accepting-deposits-to-new-addresses)
An Asset Pool is a collection of funds from various currencies and networks, all grouped together. That means all actions can be focused at the pool level without having to handle multiple addresses and key pairs to facilitate deposits and withdrawals. You can create as many as you need to handle your different funds.
To create an Asset Pool to hold all associated crypto addresses, send the [`POST /digital/v1/asset-pools`](/l1-api/digital-assets/create-asset-pool) request with the following body parameters:
```json title="Example request"
{
"name": "Asset Pool One",
"reference": "AssetPoolOne"
}
```
Upon successfully submitting the request, you receive details of the new Asset Pool:
```json title="Example response"
{
"id": "2be1cc9c-a5af-47e2-9dba-b7b82317e600",
"name": "Asset Pool One",
"reference": "AssetPoolOne"
}
```
At this stage, the Asset Pool only displays the above three options, as there are no addresses added to it yet. As long as addresses of different currencies are added, the currency and totals will appear on the relevant Asset Pools.
Once an Asset Pool is created, add an Address to it: [Create a crypto address](/layer1/use-cases/digital-assets/accepting-deposits-to-new-addresses/create-a-new-crypto-address).
---
## Receive deposit and ingest the webhooks
Once you share the address with a depositor, wait for the deposit to arrive. When Layer1 detects such a transaction, the following webhook types are sent out for each stage of the deposit process:
- `layer1:crypto:deposit:status-change` update on any change of the deposit status.
- `layer1:digital:balance:updated` give an update of the wallet balances within your Asset Pool.
## The "balance updated" webhooks
The following webhooks are sent out to update the current balance within the corresponding deposit address:
- The "funds reserved on blockchain" update shows the amount added to the `RESERVED` and the `BLOCKCHAIN` sections once the deposit is confirmed on the blockchain.
- The "available funds" update indicates that the deposit is successful, as the funds have been moved from `RESERVED` to `AVAILABLE` for this address.
```json title="Example webhook"
{
"event": "layer1:digital:balance:updated",
"timestamp": "2024-06-05T20:40:16.941138099Z",
"data": {
"reference": "First Eth Address",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9",
"asset": "ETH",
"funds": [
{
"type": "AVAILABLE",
"balance": "0.00",
"delta": "0.00"
},
{
"type": "BLOCKCHAIN",
"balance": "0.20",
"delta": "0.20"
},
{
"type": "RESERVED",
"balance": "0.20",
"delta": "0.20"
}
]
}
}
```
```json title="Example webhook"
{
"event": "layer1:digital:balance:updated",
"timestamp": "2024-06-05T20:40:31.395066159Z",
"data": {
"reference": "First Eth Address",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9",
"asset": "ETH",
"funds": [
{
"type": "AVAILABLE",
"balance": "0.20",
"delta": "0.20"
},
{
"type": "BLOCKCHAIN",
"balance": "0.20",
"delta": "0.00"
},
{
"type": "RESERVED",
"balance": "0.00",
"delta": "-0.20"
}
]
}
}
```
## The "status change" webhooks
The following webhooks are sent out to inform about the current status of the deposit:
- `DETECTED`
- `UNCONFIRMED`
- `CONFIRMED`
- `SCREENING_REQUESTED`
- `SUCCESS`
```json title="Example webhook"
{
"event": "layer1:crypto:deposit:status-change",
"timestamp": "2024-06-05T20:39:19.602586984Z",
"data": {
"id": "018fea20a69e7cc58066f8d03362e797d18a869f191944a09e5e784d7ca8f45a6f",
"hash": "0x8d49dad00612517e5fdf9ca896c4aded5a46c30226cd44f4f0c26b4c65ae63fe",
"type": "DEPOSIT",
"asset": "ETH",
"amount": "0.20",
"status": "DETECTED",
"address": {
"id": "d18a869f-1919-44a0-9e5e-784d7ca8f45a",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9",
"network": "ETHEREUM",
"keyPairId": "44cf54df-93e9-4dd9-84c9-e96f0cf90f3e",
"reference": "First Eth Address",
"customerId": "customerOne",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
},
"sources": [
{
"amount": "0.20",
"address": "0x84a4a239805d06c685219801b82bea7c76702214"
}
],
"createdAt": "2024-06-05T20:39:19.454Z",
"updatedAt": "2024-06-05T20:39:19.528756Z",
"destinations": [
{
"amount": "0.20",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9"
}
]
}
}
```
```json title="Example webhook"
{
"event": "layer1:crypto:deposit:status-change",
"timestamp": "2024-06-05T20:39:26.724877059Z",
"data": {
"id": "018fea20a69e7cc58066f8d03362e797d18a869f191944a09e5e784d7ca8f45a6f",
"hash": "0x8d49dad00612517e5fdf9ca896c4aded5a46c30226cd44f4f0c26b4c65ae63fe",
"type": "DEPOSIT",
"asset": "ETH",
"amount": "0.20",
"status": "UNCONFIRMED",
"address": {
"id": "d18a869f-1919-44a0-9e5e-784d7ca8f45a",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9",
"network": "ETHEREUM",
"keyPairId": "44cf54df-93e9-4dd9-84c9-e96f0cf90f3e",
"reference": "First Eth Address",
"customerId": "customerOne",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
},
"sources": [
{
"amount": "0.200234653675781",
"address": "0x84a4a239805d06c685219801b82bea7c76702214"
}
],
"createdAt": "2024-06-05T20:39:19.454Z",
"updatedAt": "2024-06-05T20:39:26.683343Z",
"destinations": [
{
"amount": "0.20",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9"
}
],
"networkDetail": {
"fee": {
"asset": "ETH",
"amount": "0.000234653675781"
},
"block": {
"number": 6046664,
"timestamp": "2024-06-05T20:39:24Z"
}
}
}
}
```
```json title="Example webhook"
{
"event": "layer1:crypto:deposit:status-change",
"timestamp": "2024-06-05T20:40:16.893418347Z",
"data": {
"id": "018fea20a69e7cc58066f8d03362e797d18a869f191944a09e5e784d7ca8f45a6f",
"hash": "0x8d49dad00612517e5fdf9ca896c4aded5a46c30226cd44f4f0c26b4c65ae63fe",
"type": "DEPOSIT",
"asset": "ETH",
"amount": "0.20",
"status": "CONFIRMED",
"address": {
"id": "d18a869f-1919-44a0-9e5e-784d7ca8f45a",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9",
"network": "ETHEREUM",
"keyPairId": "44cf54df-93e9-4dd9-84c9-e96f0cf90f3e",
"reference": "First Eth Address",
"customerId": "customerOne",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
},
"sources": [
{
"amount": "0.200234653675781",
"address": "0x84a4a239805d06c685219801b82bea7c76702214"
}
],
"createdAt": "2024-06-05T20:39:19.454Z",
"updatedAt": "2024-06-05T20:40:16.867302Z",
"destinations": [
{
"amount": "0.20",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9"
}
],
"networkDetail": {
"fee": {
"asset": "ETH",
"amount": "0.000234653675781"
},
"block": {
"number": 6046664,
"timestamp": "2024-06-05T20:39:24Z"
}
}
}
}
```
```json title="Example webhook"
{
"event": "layer1:crypto:deposit:status-change",
"timestamp": "2024-06-05T20:40:17.112592654Z",
"data": {
"id": "018fea20a69e7cc58066f8d03362e797d18a869f191944a09e5e784d7ca8f45a6f",
"hash": "0x8d49dad00612517e5fdf9ca896c4aded5a46c30226cd44f4f0c26b4c65ae63fe",
"type": "DEPOSIT",
"asset": "ETH",
"amount": "0.20",
"status": "SCREENING_REQUESTED",
"address": {
"id": "d18a869f-1919-44a0-9e5e-784d7ca8f45a",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9",
"network": "ETHEREUM",
"keyPairId": "44cf54df-93e9-4dd9-84c9-e96f0cf90f3e",
"reference": "First Eth Address",
"customerId": "customerOne",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
},
"sources": [
{
"amount": "0.200234653675781",
"address": "0x84a4a239805d06c685219801b82bea7c76702214"
}
],
"createdAt": "2024-06-05T20:39:19.454Z",
"updatedAt": "2024-06-05T20:40:16.973137Z",
"destinations": [
{
"amount": "0.20",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9"
}
],
"networkDetail": {
"fee": {
"asset": "ETH",
"amount": "0.000234653675781"
},
"block": {
"number": 6046664,
"timestamp": "2024-06-05T20:39:24Z"
}
}
}
}
```
```json title="Example webhook"
{
"event": "layer1:crypto:deposit:status-change",
"timestamp": "2024-06-05T20:40:31.419360016Z",
"data": {
"id": "018fea20a69e7cc58066f8d03362e797d18a869f191944a09e5e784d7ca8f45a6f",
"hash": "0x8d49dad00612517e5fdf9ca896c4aded5a46c30226cd44f4f0c26b4c65ae63fe",
"type": "DEPOSIT",
"asset": "ETH",
"amount": "0.20",
"status": "SUCCESS",
"address": {
"id": "d18a869f-1919-44a0-9e5e-784d7ca8f45a",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9",
"network": "ETHEREUM",
"keyPairId": "44cf54df-93e9-4dd9-84c9-e96f0cf90f3e",
"reference": "First Eth Address",
"customerId": "customerOne",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
},
"sources": [
{
"amount": "0.200234653675781",
"address": "0x84a4a239805d06c685219801b82bea7c76702214"
}
],
"createdAt": "2024-06-05T20:39:19.454Z",
"updatedAt": "2024-06-05T20:40:31.380741Z",
"destinations": [
{
"amount": "0.20",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9"
}
],
"networkDetail": {
"fee": {
"asset": "ETH",
"amount": "0.000234653675781"
},
"block": {
"number": 6046664,
"timestamp": "2024-06-05T20:39:24Z"
}
}
}
}
```
At the final point of getting the `SUCCESS` webhook, the deposit has been successfully received by the asset pool. This triggers an automatic consolidation round, which transfers the funds to a central master wallet that pools all the funds for future use.
---
## Recover funds sent on incorrect network
When a user makes a transaction to the correct address but over the wrong blockchain network, Layer1 can recover (or “claim”) those funds, provided the [network used is one that Layer1 supports](/layer1/references/supported-assets-and-protocols).
**Situation**:
Paige sat at her desk, staring at her screen with a growing knot in her stomach. She had carefully copied her USDT deposit address, 0xABC…123, with the intention of sending funds over the Binance (BEP-20) network. But in her haste, she’d clicked the Ethereum (ERC-20) option instead. The transfer whirred through the blockchain, and when it landed, her balance stubbornly refused to update...
**How to fix the situation**:
Recovering funds relies on creating an on‑chain address in the actual network (Ethereum) that uses the same reference (for example, `user-address-001`) as the intended address (Binance).
The process is the following:
1. Ask the user to send you the Transaction Hash (TxHash).
2. [Recreate the address](/layer1/getting-started/getting-started-in-sandbox-portal/create-addresses) on the mistaken network with the original reference.
3. Submit a claim (via Portal or API) to credit the user’s account.
To recover funds via the Portal, do the following:
1. Log in to the Layer1 Portal.
2. In the main menu, go to **Digital Assets** > **Addresses**, click **Create address**.
3. In the **Create Address** form, configure the settings, then click **Create address**:
- **Creation method**: Select **By network (single address)**.
- **Network**: Specify the actual network where the funds are. In our case, Ethereum.
- **Asset pool**: Select the asset pool of the original address.
- **Reference**: Enter the original reference (for example, `user-address-001`).
4. Go to **Digital Assets** > **Transaction history** and click **Claim transaction** in the top-right corner.
5. In **Claim a transaction**, configure the claim:
- **Asset Pool**: Select the relevant pool.
- **Network**: Choose the network from which the user sent funds (for example, Ethereum).
- **Hash**: Paste the user’s TxHash.
6. Click **Claim transaction**.
The system will process the operation. Upon successful completion, the transaction will appear in the transactions list, and the balance will be credited to the corresponding asset pool.
To recover funds via API, do the following:
1. Create a new address by sending the [`POST /digital/v1/addresses`](/l1-api/digital-assets/create-address) request with the following body parameters:
```json title="Example request"
{
"assetPoolId": "21031957-8257-4dcb-89d5-ca98b55f7dab",
"network": "Ethereum",
"reference": "user-address-001",
"asset": "USDT"
}
```
Here,
- `network`: Specify the network through which the user sent their funds.
- `reference`: Add the reference of the address to which the user was supposed to send the funds.
2. Send the [`POST digital/v1/transaction-claims`](/l1-api/digital-assets/claim-transaction) request with the following body parameters:
```json title="Example request"
{
"hash": "0xf07579bf8e120a50abe707f5e890e32f347ac4716d00bc9d293f186101d8ddc0",
"network": "ETHEREUM"
}
```
Here,
- `hash`: specify the transaction hash obtained from the user.
- `network`: specify the actual network where the funds are. In our case, Ethereum.
A successful request returns 204 No Content, indicating the claim is processing.
---
## Set up a webhook destination
To receive webhooks for all events across the Layer1 platform, you need to set up at least one webhook destination.
For the complete list of all possible webhook events that can be sent to your destination, see the [Webhooks](/l1-api/api-overview/webhooks) section of the guides. It also includes a guide on validating those webhooks.
To create a destination address to which all webhooks of a specific subscribed event will be sent, send the [`POST /notification/v1/destinations`](/l1-api/webhooks/create-destination) request with the following body parameters:
```json title="Body parameters"
{
"url": "https://hooks.slack.com/services/T88HSLB/B08360RN3/sh6EydWDblDf",
"type": "SLACK",
"events": [
"layer1:crypto:address:created",
"layer1:crypto:deposit:status-change",
"layer1:crypto:withdrawal:status-change",
"layer1:crypto:consolidation:status-change",
"layer1:notification"
]
}
```
Within the `events` array, the following events are available for subscription and triggered when:
- A new trade conversion is created: `layer1:trade:conversion:created`.
- The state of a trade conversion changes: `layer1:trade:conversion:state-changed`.
- A new address is created at a trading venue:`layer1:trade:venue:address:created`.
- A new withdrawal is created at a trading venue:`layer1:trade:venue:withdrawal:created`.
- The state of a withdrawal changes at a trading venue: `layer1:trade:venue:withdrawal:state-changed`.
- A new address is created: `layer1:crypto:address:created`.
- The status of a deposit changes: `layer1:crypto:deposit:status-change`.
- The status of a withdrawal changes: `layer1:crypto:withdrawal:status-change`.
- The status of a consolidation changes: `layer1:crypto:consolidation:status-change`.
- The balance is updated: `layer1:digital:balance:updated`.
You can add any number of these events to a destination, and create as many destinations as you need to.
Here is how an example request might look:
```bash title="Example cURL"
curl --location 'https://api.sandbox.layer1.com/notification/v1/destinations' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer eyJhbG...QQ73OA' \
--data '{
"url": "https://webhook.site/52847769-cf41-4f94-8bec-57bfad257770",
"type": "WEB",
"events": [
"layer1:trade:conversion:created",
"layer1:trade:conversion:state-changed",
"layer1:trade:venue:address:created",
"layer1:trade:venue:withdrawal:created",
"layer1:trade:venue:withdrawal:state-changed",
"layer1:crypto:address:created",
"layer1:crypto:deposit:status-change",
"layer1:crypto:withdrawal:status-change",
"layer1:crypto:consolidation:status-change",
"layer1:digital:balance:updated"
]
}'
```
Upon successfully submitting the request, you'll receive a response like the following:
```json title="Example Response"
{
"url": "https://webhook.site/52847769-cf41-4f94-8bec-57bfad257770",
"type": "WEB",
"eventCodes": [
"layer1:trade:venue:withdrawal:created",
"layer1:crypto:address:created",
"layer1:trade:conversion:state-changed",
"layer1:trade:venue:address:created",
"layer1:crypto:consolidation:status-change",
"layer1:crypto:deposit:status-change",
"layer1:trade:conversion:created",
"layer1:digital:balance:updated",
"layer1:trade:venue:withdrawal:state-changed",
"layer1:crypto:withdrawal:status-change"
],
"publicKey": "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAE+t4UzaqR68OQb/XnE1DjLk8AiD8bD69fbYZt+44fqRbnywMynzOZFqQWribhQDmdDc8gL6NzBODS1HtZSUIIJw=="
}
```
:::warning
Pay special attention to your `publicKey` when validating the webhooks sent to the listed URL.
:::
To get the `publicKey` again, or see what current events you are subscribed to across multiple destinations, send the [`GET /notification/v1/destinations`](/l1-api/webhooks/list-destinations) request. It retrieves the destination created on the previous page, as well as any other destinations on your account.
---
## Receive deposits
This guide outlines the process for accepting deposits into a unique address created for the transaction. It provides a detailed walkthrough of the deposit process, including how it interacts with the respective blockchain, the prerequisites of creating an asset pool and a webhook destination for payment event updates. Once the pre-steps are done, it covers which endpoint is used to create the address, the data that needs to be sent to the endpoint, and how the funds are consolidated into a master address that belongs to the asset pool after the deposit is received. Additionally, it covers the webhooks sent out during the withdrawal process.
To integrate address deposits, you must complete the following steps:
1. [Set up the webhooks destination](/layer1/use-cases/digital-assets/accepting-deposits-to-new-addresses/set-up-a-webhook-destination) to send payment update webhooks and any other related events.
2. [Create the asset pool](/layer1/use-cases/digital-assets/accepting-deposits-to-new-addresses/create-an-asset-pool) that will house the created address and keep track of all funds sent to any related addresses created in future.
3. [Create a new crypto Address](/layer1/use-cases/digital-assets/accepting-deposits-to-new-addresses/create-a-new-crypto-address) for the specific currency wanted. The final address is sent to the earlier webhook destination.
4. [Ingest and deserialise the resulting webhooks](/layer1/use-cases/digital-assets/accepting-deposits-to-new-addresses/receive-deposit-and-ingest-the-webhooks) to keep track of:
- Created address
- Status of the deposits sent in
- Total amount of funds in your pool
- Movement of funds within your asset pool
To summarise, this deposit process involves the following:
1. Sending funds to a wallet address created and linked to your asset pool.
2. Once deposits are sent to the address, the Layer1 system will pick up the transaction, and the status of the deposit will be updated through webhooks.
3. Once funds have been recorded within the asset pool, a consolidation phase will occur, transferring the funds into a master wallet so that they can be easily sent out as required.
## Deposit flow
The deposits pass through the following lifecycle on the Layer1 platform:
1. Once a payment is first detected on the blockchain, a transaction with type DEPOSIT and status DETECTED is created.
2. The respective blockchain will process the transaction, and it checks for errors
- If errors are found (YES), then the DEPOSIT is assigned as FAILED, as this action is irreversible.
- If no errors are found (NO), the DEPOSIT status is updated to UNCONFIRMED and is monitored until the desired confirmation threshold is reached. For more information on confirmation thresholds, see [Supported Assets and Networks](/layer1/references/supported-assets-and-protocols)
3. Once the required number of confirmations is reached the DEPOSIT is moved to CONFIRMED state.
4. Another check is made to see if the transaction needs to be screened against any fraudulent activity.
- If NO checks are required, the state changes to SUCCESS and the funds become available within the asset pool.
- If the transaction must be screened (YES), the state is updated to SCREENING_REQUESTED.
5. An automatic screening check is performed.
- If the funds can be released (YES), the state changes to SUCCESS and the funds are available within the asset pool.
- If Travel Rule is enabled and the screening provider determines that originator or beneficiary information is missing or incomplete, the DEPOSIT is set to HELD. The transaction will include a `requiredAction` of type `MISSING_COMPLIANCE_DETAILS`. To unblock the deposit, submit the missing compliance data via the address actions endpoint. The deposit is automatically re-screened once the data is provided and released if screening passes. For tag-based networks (Ripple/XRP, TRON, Solana), if the destination tag itself is missing, the deposit is held before screening begins with a `requiredAction` of type `MISSING_TAG`, and a tag must be created first.
- Otherwise (NO), the DEPOSIT is held for manual review and set to the HELD state.
6. The deposit is then checked for manual approval.
- If the DEPOSIT is checked and found to be eligible for approval, the screening can be APPROVED. The DEPOSIT state changes to SUCCESS. The funds become available within the asset pool.
- If the DEPOSIT is eligible to be returned, from the HELD status you can trigger RETURN. This will update the DEPOSIT to REJECTED and a new RETURN WITHDRAWAL is created.

### Deposit Statuses
A Deposit will go through a series of statuses in the deposit workflow:
| **Status** | **Description** |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| **`Detected`** | The deposit has been detected and distributed on the blockchain, but does not yet have any confirmations. Optional. |
| **`Failed`** | The deposit failed due to an on-chain error during execution, which could have multiple underlying causes. |
| **`Unconfirmed`** | The deposit has had one or more blockchain confirmations, but has not yet reached the required confirmation limit. Optional. |
| **`Confirmed`** | The deposit has reached the required confirmation limit and is not fully confirmed on the blockchain.If you have a confirmation threshold of one block, you will always get a confirmed event. |
| **`Screening_Requested`** | The deposit has been flagged by the compliance provider and requires screening. |
| **`Held`** | The deposit has been held for manual review after screening, or because Travel Rule is enabled and compliance data (originator/beneficiary information or a destination tag) is required before screening can proceed. Check the `requiredAction` field on the transaction to determine next steps. |
| **`Success`** | The deposit has been processed, and the funds are available in the asset pool. |
| **`Rejected`** | The deposit was not found eligible for approval and was rejected. |
Now that you understand the different statuses and processes that a transaction goes through, you can set up your system to start receiving deposits as shown in the following guides:
- [Set up a webhook destination](/layer1/use-cases/digital-assets/accepting-deposits-to-new-addresses/set-up-a-webhook-destination)
- [Create an asset pool](/layer1/use-cases/digital-assets/accepting-deposits-to-new-addresses/create-an-asset-pool)
- [Create a crypto address](/layer1/use-cases/digital-assets/accepting-deposits-to-new-addresses/create-a-new-crypto-address)
- [Receive deposit and ingest the webhooks](/layer1/use-cases/digital-assets/accepting-deposits-to-new-addresses/receive-deposit-and-ingest-the-webhooks)
- [Auto-consolidate assets in master wallet](/layer1/use-cases/digital-assets/accepting-deposits-to-new-addresses/automatic-fund-conslidation)
- [Recover funds sent on incorrect network](/layer1/use-cases/digital-assets/accepting-deposits-to-new-addresses/recover-funds-from-incorrect-network)
---
## Add a contact
Contacts enable you to manage trusted addresses for both first- and third-party accounts to which you regularly send crypto. This allows for seamless transfers and withdrawals, while also enhancing fund security by minimising the risk of sending funds to an incorrect address.
### Add a new contact
To add a new contact, do the following:
1. On the Portal, go to **Digital Assets** > **Contacts** and select **Add contact**.
2. Select the type of contact you want to create: Company or Individual, and complete the information required. Name and description fields are required. All other fields are optional but provide a valuable way for you to store information about a contact which may be needed for travel rule compliance purposes.
Contacts with the Company and Individual contact types" width="700" />
3. Select **Add Contact** to create the contact. You will now see this contact appear in your contact list.
### Add a trusted address for a contact
To add a new trusted address for a contact, do the following:
1. Go to **Digital Assets** > **Contacts** and find the contact for which you want to add an address.
2. In the selected contact, click **Manage Addresses**, and then click **Add address**.
3. In **Add trusted address**, enter a **Description** to label the address, and select the **Asset & Network** of the address, for example, BTC (BITCOIN).
4. In the **Address** field, enter your crypto address.
5. Click **Add address** to save the address.
### Restrict withdrawals to trusted addresses and require approval of trusted addresses
Storing trusted addresses allows you to add a layer of security to your fund withdrawal process by removing the need to copy and paste addresses you use frequently and reducing human error.
You can go further and add additional layers of security via Approval settings.
To restrict withdrawals to trusted addresses only, do the following:
1. Go to **Approvals** > **Settings**.
2. Select the tab **Withdrawals** and click **Edit**.
3. Under **Allow withdrawals to**, select **Trusted addresses only (recommended)**.
4. Click **Save**.
To require approvals for adding and deleting trusted addresses, do the following:
1. Before you start, set up an approver by assigning the `contact:address:approver` role to a user in **Manage account** > **Team members**.
2. Go to **Approvals** > **Settings**.
3. Select the tab **Trusted addresses** and click **Edit**.
4. Enable the rule to **Require an approval when trusted addresses are created and deleted**.
5. Select the number of approvers you require.
6. Click **Save**.
---
## Approve withdrawals
To set up approvals for your Layer1 account, first follow the steps outlined in the [Configure Approval for Withdrawals ](/layer1/use-cases/digital-assets/creating-a-withdrawal-transaction-request/configure-approval-settings) guide.
To approve a withdrawal, you must have the [`crypto:withdrawal:approver` role](/layer1/references/roles#approvals).
To approve a pending withdrawal, do the following:
1. On the Layer1 Portal, go to **Approvals** and open the **Pending** tab.
2. Find the transaction you want to review, and click **Approve** or **Reject**. To act on several requests at once, select them and click **Approve** or **Reject** above the list.
Approved and rejected transactions appear in the **Approved** and **Rejected** tabs, respectively.
Pending withdrawals expire after seven days and can be viewed in the **Expired** tab.
---
## Configure approvals for withdrawals
Approval settings allow you to configure rules to govern outgoing transactions from your asset pools. By setting volume-based thresholds, you can require approvals for all withdrawals or those exceeding specific thresholds.
## Why do you need approvals for withdrawals?
Requiring approvals for withdrawals adds a critical layer of control and oversight to your Layer1 treasury movements. This feature provides the following benefits:
- **Risk Mitigation**.
Approvals reduce the risk of accidental, unauthorised, or malicious transfers. By introducing a review step, you can prevent large or suspicious withdrawals from being executed without scrutiny.
- **Customizable Control**.
Teams can configure thresholds and delegate approvers, allowing necessary stakeholders or leads to stay informed about high-impact transactions while enabling routine operations to continue smoothly.
- **Auditability and Transparency**.
All approvals (or rejections) are logged and traceable. This strengthens accountability across teams and simplifies post-transaction reviews or compliance audits.
- **Peace of Mind for Stakeholders**.
Ultimately, this feature gives teams confidence that funds are protected by policy and oversight, not just technical safeguards—ensuring trust at both operational and executive levels.
## Restrict withdrawals to trusted addresses
To increase security surrounding fund withdrawals it is recommended to limit withdrawals to trusted addresses only. Trusted addresses are addresses linked to a contact you have created within your Layer1 instance. For more information on creating contacts, see [Add a contact](/layer1/use-cases/digital-assets/add-a-contact).
To restrict withdrawals to trusted addresses only, do the following:
1. Go to **Approvals** > **Settings**.
2. Select the tab **Withdrawals** and click **Edit** next to **Allow withdrawals to**.
3. Select **Trusted addresses only (recommended)**.
4. Click **Save**.
## Enable approvals for withdrawals
By default, no approvals are required for withdrawals from your Layer1 asset pools.
To enable approvals for withdrawals or to edit configurations, you must have the role `approval-settings:operator`.
### Configure approvals for all transactions
To require approvals for **all transactions** of **any amount**, do the following:
1. Go to **Approvals** > **Settings**.
2. Select the tab **Withdrawals** and click **Edit** next to the source you want to configure approvals for: **Withdrawals made via Layer1 portal** or **Withdrawals made via API**.
3. Select **Require approval for all transactions**.
4. Use the dropdown to select the number of approvers required to sanction withdrawals.
5. Click **Save**.
Ensure you **[Assign approver permissions](/layer1/use-cases/digital-assets/creating-a-withdrawal-transaction-request/configure-approval-settings#assign-approver-permissions)** to relevant users to complete your approvals set-up.
### Configure approvals for transaction ranges
To require approvals for **transaction ranges**, do the following:
1. Go to **Approvals** > **Settings**.
2. Select the tab **Withdrawals** and click **Edit** next to the source you want to configure approvals for: **Withdrawals made via Layer1 portal** or **Withdrawals made via API**.
3. Select **Require approvals for transaction ranges**.
4. **Set your first transaction range**:
1. In the **to** field, enter the upper limit of the range. Transaction ranges must be mutually exclusive and collectively exhaustive (MECE) to avoid security setting conflicts; that's why the first range must begin with a **from** value of 0.
2. In the **Require** box, select the number of approvers required to sanction withdrawals.
5. **Set your next transaction range**:
1. Adjust the **from** and **to** fields. The form will automatically populate the **from** value based on the previous range to ensure the ranges are MECE.
2. In the **Require** box, select the number of approvers required to sanction withdrawals.
6. To add additional transaction ranges, click the **plus** icon and repeat steps 3.1-3.2.
To remove a transaction range, click the **trash** icon next to the unnecessary section.
7. Click **Save**.
Ensure you **[Assign approver permissions](/layer1/use-cases/digital-assets/creating-a-withdrawal-transaction-request/configure-approval-settings#assign-approver-permissions)** to relevant users to complete your approvals set-up.
### Configure approvals with velocity rules
Velocity rules require approvals based on withdrawal activity over a period of time, rather than on a single withdrawal. To use them, select **Require approval for velocity rules** for a source on the **Withdrawals** tab.
Each velocity rule tracks either the **Total withdrawal amount** or the **Total number of transactions** within a **Rolling time window**, for example 1 hour or 24 hours. Within each rule, you set ranges with a **From** and **To** value and the number of **Approvers required** for each range. You can add several rules for the same source; for example, one rule for the hourly withdrawal amount and another for the number of transactions per hour.
Ensure you **[Assign approver permissions](/layer1/use-cases/digital-assets/creating-a-withdrawal-transaction-request/configure-approval-settings#assign-approver-permissions)** to relevant users to complete your approvals set-up.
## Update and remove approvals configuration
To update your configuration for withdrawals, do the following:
1. Go to **Approvals** > **Settings**.
2. Select the tab **Withdrawals** and click **Edit** next to the source you want to update approvals for: **Withdrawals made via Layer1 portal** or **Withdrawals made via API**.
3. Change the approval configuration:
- **Update:** To update the approval configuration you have set, edit the number of approvers or transaction ranges required.
- **Remove:** To remove approvals for all withdrawals:
1. Select **Don't require approval**.
4. Click **Save** to store your updated settings.
## Require approvals for changes to approval settings
By default, no approvals are required for changes made to approval settings. To enable approvals for changes to settings you must have the role `approval-settings:operator`.
To require approvals for **changes to approval settings**, do the following:
1. Go to **Approvals** > **Settings**.
2. Select the tab **Approvals governance** and click **Edit**.
3. Select **Require approvals when changes to approval settings are made**
4. Use the dropdown to select the number of approvals you require
5. Click **Save**
Ensure you **[Assign approver permissions](/layer1/use-cases/digital-assets/creating-a-withdrawal-transaction-request/configure-approval-settings#assign-approver-permissions)** to relevant users to complete your approvals set-up.
## Assign approver permissions
To update user permissions, you must have the [`user:operator` role](/layer1/references/roles).
To approve withdrawals, you must have the `crypto:withdrawal:approver` permission.
To assign this approver permission to users, do the following:
1. Go to **Manage account** > **Team members**.
2. Find the user to whom you want to assign approver permissions, open the menu (**⋮**) next to their name, and click **Edit**.
3. In the **Assign roles to user** menu, click the expander arrow to find the `crypto:withdrawal:approver` role and assign it to the user.
4. Click **Update**.
---
## Create withdrawal transaction
To create a withdrawal transaction, send [`POST /digital/v1/transaction-requests`](/l1-api/digital-assets/create-transaction) request with the following body parameters:
```json title="Body parameters"
{
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600",
"asset": "ETH",
"network": "ETHEREUM",
"reference": "myUniqueRef333",
"destinations": [
{
"address": "0x84A4a239805d06c685219801B82BEA7c76702214",
"amount": "0.001",
"asset": "ETH"
}
]
}
```
Upon successfully submitting the transaction request, you receive the following details:
```json title="Example Response"
{
"requestId": "018fef0a-ab15-7afb-966b-7d20c89b5504",
"asset": "ETH",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600",
"network": "ETHEREUM",
"reference": "myUniqueRef333",
"status": "CREATED",
"sources": [],
"destinations": [
{
"address": "0x84A4a239805d06c685219801B82BEA7c76702214",
"amount": "0.001"
}
],
"type": "WITHDRAWAL",
"createdAt": "2024-06-06T19:33:24.885Z"
}
```
The withdrawal is now actioned and moves throughout the flow described previously. You can use the `requestId` along with the [Get transaction request](/l1-api/digital-assets/get-transaction-request-by-id) endpoint to poll the withdrawal at any point.
At this point, the Layer1 platform sends out webhooks related to the withdrawal, which will be covered in the next section, [Receive withdrawal notifications](/layer1/use-cases/digital-assets/creating-a-withdrawal-transaction-request/understanding-the-withdrawal-webhooks).
---
## Receive withdrawal notifications
Once the transaction has been submitted, the following webhooks are sent out for each stage of the withdrawal process.
- `layer1:crypto:withdrawal:status-change` updates on any change of the withdrawal status.
- `layer1:digital:balance:updated` gives an update of the wallet balances within your Asset Pool.
## The "balance updated" webhooks
The following webhooks are sent out to update the current balance within the corresponding master address:
- The "funds reserved" update indicates the withdrawal amount has been moved from [`AVAILABLE` to `RESERVED` balance](/layer1/use-cases/digital-assets/creating-a-withdrawal-transaction-request#withdrawal-flow).
- The "funds released" update indicates that the funds have been removed from the `RESERVED` pool following a successful withdrawal.
```json title="Example webhook"
{
"event": "layer1:digital:balance:updated",
"timestamp": "2024-06-06T19:33:25.059079161Z",
"data": {
"reference": "AssetPoolTwo",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d",
"asset": "ETH",
"funds": [
{
"type": "AVAILABLE",
"balance": "0.19848315335156838671875",
"delta": "-0.00114716058324173828125"
},
{
"type": "BLOCKCHAIN",
"balance": "0.199630313934810125",
"delta": "0.00"
},
{
"type": "RESERVED",
"balance": "0.00114716058324173828125",
"delta": "0.00114716058324173828125"
}
]
}
}
```
```json title="Example webhook"
{
"event": "layer1:digital:balance:updated",
"timestamp": "2024-06-06T19:34:38.763517740Z",
"data": {
"reference": "AssetPoolTwo",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d",
"asset": "ETH",
"funds": [
{
"type": "AVAILABLE",
"balance": "0.198496131149297125",
"delta": "0.000012977797728738"
},
{
"type": "BLOCKCHAIN",
"balance": "0.198496131149297125",
"delta": "-0.001134182785513"
},
{
"type": "RESERVED",
"balance": "0.00",
"delta": "-0.001147160583241738"
}
]
}
}
```
## The "status change" webhooks
The following webhooks are sent out to inform about the current status of the withdrawal:
| **Status** | **Description** |
| :------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`CREATED`** | The withdrawal request is first registered with the created status. |
| **`FUNDS_NEEDED`** | The withdrawal is paused if there are insufficient funds within the asset pool to action the withdrawal. The transaction automatically progresses once matching funds are detected, or is rejected after 24 hours. |
| **`FUNDS_REQUESTED`** | A top up of funds has been requested to ensure enough balance is present to process withdrawal. |
| **`REJECTED`** | The withdrawal is rejected if there are insufficient funds within the asset pool to action the withdrawal. |
| **`FUNDS_RESERVED`** | The withdrawal has moved funds from the available balance to the reserved balance. |
| **`APPROVAL_REQUESTED`** | The withdrawal requires approval and is pending review by approvers. |
| **`SCREENING_REQUESTED`** | The withdrawal has moved into the screening phase and requires screening. |
| **`HELD`** | The withdrawal has been held to be manually reviewed. |
| **`SCREENING_PASSED`** | The withdrawal has passed screening, manual or automatic. |
| **`SIGNED`** | The withdrawal is signed and submitted to the blockchain. |
| **`DETECTED`** | The withdrawal has been detected and distributed on the blockchain, but does not yet have any confirmations. At this stage, the transaction is already on the relevant blockchain and can now be traced with the `hash` provided in the webhook. |
| **`FAILED`** | The withdrawal failed due to an on-chain error that occurred during execution, which could be due to multiple underlying reasons. |
| **`UNCONFIRMED`** | The withdrawal has had one or more blockchain confirmations, but has not yet reached the required confirmation limit. |
| **`CONFIRMED`** | The withdrawal has reached the required confirmation limit and is not fully confirmed on the blockchain. |
| **`SUCCESS`** | The withdrawal is successful, and the balance of the asset pool is updated to reflect this. |
| **`BLOCKED`** | An unexpected failure has occurred. Withdrawal must be checked by support before retrying. |
| **`REPLACED`** | An unexpected failure has occurred and manual action was required by the Layer1 team. The replaced withdrawal will have the same `reference` value. |
### Most common webhook examples
```json title="Example webhook"
{
"event": "layer1:crypto:withdrawal:status-change",
"timestamp": "2024-06-06T19:33:25.139504437Z",
"data": {
"id": "018fef0aab157afb966b7d20c89b55048e1fcdb58edc49b792891c41c5bcd81869",
"type": "WITHDRAWAL",
"asset": "ETH",
"amount": "0.001",
"status": "CREATED",
"address": {
"id": "8e1fcdb5-8edc-49b7-9289-1c41c5bcd818",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d",
"network": "ETHEREUM",
"keyPairId": "fdc9f5a0-698c-4163-ab21-df85f343a2a4",
"reference": "AssetPoolTwo",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
},
"sources": [
{
"amount": "0.000147160583241738",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d"
},
{
"amount": "0.001",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d"
}
],
"createdAt": "2024-06-06T19:33:24.885Z",
"reference": "myUniqueRef333",
"updatedAt": "2024-06-06T19:33:25.109693Z",
"destinations": [
{
"amount": "0.001",
"address": "0x84A4a239805d06c685219801B82BEA7c76702214"
}
]
}
}
```
```json title="Example webhook"
{
"event": "layer1:crypto:withdrawal:status-change",
"timestamp": "2024-06-06T19:33:25.332170456Z",
"data": {
"id": "018fef0aab157afb966b7d20c89b55048e1fcdb58edc49b792891c41c5bcd81869",
"type": "WITHDRAWAL",
"asset": "ETH",
"amount": "0.001",
"status": "SCREENING_REQUESTED",
"address": {
"id": "8e1fcdb5-8edc-49b7-9289-1c41c5bcd818",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d",
"network": "ETHEREUM",
"keyPairId": "fdc9f5a0-698c-4163-ab21-df85f343a2a4",
"reference": "AssetPoolTwo",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
},
"sources": [
{
"amount": "0.000147160583241738",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d"
},
{
"amount": "0.001",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d"
}
],
"createdAt": "2024-06-06T19:33:24.885Z",
"reference": "myUniqueRef333",
"updatedAt": "2024-06-06T19:33:25.298837Z",
"destinations": [
{
"amount": "0.001",
"address": "0x84A4a239805d06c685219801B82BEA7c76702214"
}
]
}
}
```
```json title="Example webhook"
{
"event": "layer1:crypto:withdrawal:status-change",
"timestamp": "2024-06-06T19:33:26.038587396Z",
"data": {
"id": "018fef0aab157afb966b7d20c89b55048e1fcdb58edc49b792891c41c5bcd81869",
"type": "WITHDRAWAL",
"asset": "ETH",
"amount": "0.001",
"status": "FUNDS_RESERVED",
"address": {
"id": "8e1fcdb5-8edc-49b7-9289-1c41c5bcd818",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d",
"network": "ETHEREUM",
"keyPairId": "fdc9f5a0-698c-4163-ab21-df85f343a2a4",
"reference": "AssetPoolTwo",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
},
"sources": [
{
"amount": "0.000147160583241738",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d"
},
{
"amount": "0.001",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d"
}
],
"createdAt": "2024-06-06T19:33:24.885Z",
"reference": "myUniqueRef333",
"updatedAt": "2024-06-06T19:33:25.987397Z",
"destinations": [
{
"amount": "0.001",
"address": "0x84A4a239805d06c685219801B82BEA7c76702214"
}
]
}
}
```
```json title="Example webhook"
{
"event": "layer1:crypto:withdrawal:status-change",
"timestamp": "2024-06-06T19:33:26.620045369Z",
"data": {
"id": "018fef0aab157afb966b7d20c89b55048e1fcdb58edc49b792891c41c5bcd81869",
"type": "WITHDRAWAL",
"asset": "ETH",
"amount": "0.001",
"status": "SIGNED",
"address": {
"id": "8e1fcdb5-8edc-49b7-9289-1c41c5bcd818",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d",
"network": "ETHEREUM",
"keyPairId": "fdc9f5a0-698c-4163-ab21-df85f343a2a4",
"reference": "AssetPoolTwo",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
},
"sources": [
{
"amount": "0.000147160583241738",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d"
},
{
"amount": "0.001",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d"
}
],
"createdAt": "2024-06-06T19:33:24.885Z",
"reference": "myUniqueRef333",
"updatedAt": "2024-06-06T19:33:26.596896Z",
"destinations": [
{
"amount": "0.001",
"address": "0x84A4a239805d06c685219801B82BEA7c76702214"
}
]
}
}
```
```json title="Example webhook"
{
"event": "layer1:crypto:withdrawal:status-change",
"timestamp": "2024-06-06T19:33:31.090682810Z",
"data": {
"id": "018fef0aab157afb966b7d20c89b55048e1fcdb58edc49b792891c41c5bcd81869",
"hash": "0xd5048b414557978a4c271998b3fda04b0f6228edbdb47500a045f12bf22b1ee2",
"type": "WITHDRAWAL",
"asset": "ETH",
"amount": "0.001",
"status": "DETECTED",
"address": {
"id": "8e1fcdb5-8edc-49b7-9289-1c41c5bcd818",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d",
"network": "ETHEREUM",
"keyPairId": "fdc9f5a0-698c-4163-ab21-df85f343a2a4",
"reference": "AssetPoolTwo",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
},
"sources": [
{
"amount": "0.001",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d"
}
],
"createdAt": "2024-06-06T19:33:24.885Z",
"reference": "myUniqueRef333",
"updatedAt": "2024-06-06T19:33:31.046673Z",
"destinations": [
{
"amount": "0.001",
"address": "0x84a4a239805d06c685219801b82bea7c76702214"
}
]
}
}
```
```json title="Example webhook"
{
"event": "layer1:crypto:withdrawal:status-change",
"timestamp": "2024-06-06T19:33:38.477488250Z",
"data": {
"id": "018fef0aab157afb966b7d20c89b55048e1fcdb58edc49b792891c41c5bcd81869",
"hash": "0xd5048b414557978a4c271998b3fda04b0f6228edbdb47500a045f12bf22b1ee2",
"type": "WITHDRAWAL",
"asset": "ETH",
"amount": "0.001134182785513",
"status": "UNCONFIRMED",
"address": {
"id": "8e1fcdb5-8edc-49b7-9289-1c41c5bcd818",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d",
"network": "ETHEREUM",
"keyPairId": "fdc9f5a0-698c-4163-ab21-df85f343a2a4",
"reference": "AssetPoolTwo",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
},
"sources": [
{
"amount": "0.001134182785513",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d"
}
],
"createdAt": "2024-06-06T19:33:24.885Z",
"reference": "myUniqueRef333",
"updatedAt": "2024-06-06T19:33:38.447726Z",
"destinations": [
{
"amount": "0.001",
"address": "0x84a4a239805d06c685219801b82bea7c76702214"
}
],
"networkDetail": {
"fee": {
"asset": "ETH",
"amount": "0.000134182785513"
},
"block": {
"number": 6053161,
"timestamp": "2024-06-06T19:33:36Z"
}
}
}
}
```
```json title="Example webhook"
{
"event": "layer1:crypto:withdrawal:status-change",
"timestamp": "2024-06-06T19:34:38.719132837Z",
"data": {
"id": "018fef0aab157afb966b7d20c89b55048e1fcdb58edc49b792891c41c5bcd81869",
"hash": "0xd5048b414557978a4c271998b3fda04b0f6228edbdb47500a045f12bf22b1ee2",
"type": "WITHDRAWAL",
"asset": "ETH",
"amount": "0.001134182785513",
"status": "CONFIRMED",
"address": {
"id": "8e1fcdb5-8edc-49b7-9289-1c41c5bcd818",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d",
"network": "ETHEREUM",
"keyPairId": "fdc9f5a0-698c-4163-ab21-df85f343a2a4",
"reference": "AssetPoolTwo",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
},
"sources": [
{
"amount": "0.001134182785513",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d"
}
],
"createdAt": "2024-06-06T19:33:24.885Z",
"reference": "myUniqueRef333",
"updatedAt": "2024-06-06T19:34:38.697222Z",
"destinations": [
{
"amount": "0.001",
"address": "0x84a4a239805d06c685219801b82bea7c76702214"
}
],
"networkDetail": {
"fee": {
"asset": "ETH",
"amount": "0.000134182785513"
},
"block": {
"number": 6053161,
"timestamp": "2024-06-06T19:33:36Z"
}
}
}
}
```
```json title="Example webhook"
{
"event": "layer1:crypto:withdrawal:status-change",
"timestamp": "2024-06-06T19:34:38.822842496Z",
"data": {
"id": "018fef0aab157afb966b7d20c89b55048e1fcdb58edc49b792891c41c5bcd81869",
"hash": "0xd5048b414557978a4c271998b3fda04b0f6228edbdb47500a045f12bf22b1ee2",
"type": "WITHDRAWAL",
"asset": "ETH",
"amount": "0.001134182785513",
"status": "SUCCESS",
"address": {
"id": "8e1fcdb5-8edc-49b7-9289-1c41c5bcd818",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d",
"network": "ETHEREUM",
"keyPairId": "fdc9f5a0-698c-4163-ab21-df85f343a2a4",
"reference": "AssetPoolTwo",
"assetPoolId": "2be1cc9c-a5af-47e2-9dba-b7b82317e600"
},
"sources": [
{
"amount": "0.001134182785513",
"address": "0x96073c4b415d60ade18de32e43efc2914731e03d"
}
],
"createdAt": "2024-06-06T19:33:24.885Z",
"reference": "myUniqueRef333",
"updatedAt": "2024-06-06T19:34:38.802803Z",
"destinations": [
{
"amount": "0.001",
"address": "0x84a4a239805d06c685219801b82bea7c76702214"
}
],
"networkDetail": {
"fee": {
"asset": "ETH",
"amount": "0.000134182785513"
},
"block": {
"number": 6053161,
"timestamp": "2024-06-06T19:33:36Z"
}
}
}
}
```
```json title="Example webhook"
{
"event": "layer1:crypto:withdrawal:status-change",
"eventId": "0198f073-80fa-7937-b9d0-ec65cd720d46",
"timestamp": "2025-08-28T11:32:38.266465881Z",
"data": {
"id": "0198f0734ca67f07b2dd2dce33b0d2bdbe82d3b6724241288626f199041bc2986955534454",
"type": "WITHDRAWAL",
"asset": "USDT",
"amount": "111.00",
"status": "HELD",
"address": {
"id": "be82d3b6-7242-4128-8626-f199041bc298",
"address": "TJ61SeQ22JZXAH5kkWky6y4Eas7Cc5zfvZ",
"network": "TRON",
"keyPairId": "025aaa9f-95dc-49be-b205-ecba5c0724bf",
"reference": "AssetPoolTwo",
"assetPoolId": "99a25ffd-ab8d-4445-baf9-b10172039789"
},
"sources": [
{
"asset": "TRX",
"amount": "13.3735",
"address": "TJ61SeQ22JZXAH5kkWky6y4Eas7Cc5zfvZ"
},
{
"asset": "USDT",
"amount": "111.00",
"address": "TJ61SeQ22JZXAH5kkWky6y4Eas7Cc5zfvZ"
}
],
"createdAt": "2025-08-28T11:32:24.870Z",
"reference": "myUniqueRef333",
"updatedAt": "2025-08-28T11:32:38.242953Z",
"destinations": [
{
"asset": "USDT",
"amount": "111.00",
"address": "TEAhQNyqfPds5d3ztLZbyiwLHqBFWN7k7r"
}
]
}
}
```
---
## Send withdrawals
This guide outlines the process for integrating withdrawals into your platform. It provides a detailed walkthrough of the withdrawal process, including how it interacts with the respective blockchain, which endpoint is used to create the payout, the data that needs to be sent to the endpoint, and what that data represents. Additionally, it covers the webhooks sent out during the withdrawal process.
To integrate the withdrawal feature, you must complete the following steps:
1. [Set up the webhooks destination](/layer1/use-cases/digital-assets/accepting-deposits-to-new-addresses/set-up-a-webhook-destination) to send update notifications and any other related events. The webhooks you need are the following:
- `layer1:crypto:withdrawal:status-change`
- `layer1:digital:balance:updated`
- `layer1:approval:request:vote-required` only if using [approvals](/layer1/use-cases/digital-assets/creating-a-withdrawal-transaction-request/configure-approval-settings)
2. [Create the asset pool](/layer1/use-cases/digital-assets/accepting-deposits-to-new-addresses/create-an-asset-pool) that will house the created address and keep track of all funds sent to any related addresses created in future.
3. Ensure that funds are available in the asset pool to withdraw. For the details, refer to the [Create a new address](/layer1/use-cases/digital-assets/accepting-deposits-to-new-addresses/create-a-new-crypto-address) and [Receive Deposit and Ingest the Webhooks](/layer1/use-cases/digital-assets/accepting-deposits-to-new-addresses/receive-deposit-and-ingest-the-webhooks) guides.
To summarise, the withdrawal process involves the following:
1. Creating a Withdrawal Transaction via the **Create transaction request** endpoint. The outbound wallet address is sent in the request.
2. Ingesting and deserialising the resulting webhooks to keep track of your pool funds and the status of the withdrawal.
## Withdrawal Flow
The withdrawals pass through the following lifecycle on the Layer1 platform:
1. A new transaction request is registered with status **CREATED** and type **WITHDRAWAL**.
2. The system checks if the asset pool has sufficient funds to process the withdrawal:
#### a. If sufficient funds are available:
- The transaction proceeds to reserve the funds.
#### b. If funds are insufficient:
- Status updates to **FUNDS_NEEDED**
Two outcomes are possible while in **FUNDS_NEEDED**
- **Liquidity becomes available:**
- The transaction automatically retries once matching funds are detected.
- No manual action is needed if the new liquidity matches the transaction's pool, network, and asset.
- Liquidity can come from:
- New deposits
- Internal consolidations
- Manual top-ups by your team
- **Timeout expires (after 24 hours):**
- The transaction is automatically rejected.
- Status updates to **REJECTED**
3. During funds reservation, the required amount is moved from the available balance to the reserved balance of the source addresses. The status is updated to **FUNDS_RESERVED**.
4. Once funds are reserved, the transaction is reviewed against approval rules (for example, requires approval for any withdrawal over $10,000).
1. If approval is required, the status changes to **APPROVAL_REQUESTED**.
2. If no approval is required, the transaction moves to compliance screening. The status becomes **SCREENING_REQUESTED**.
5. The transaction then undergoes automated screening:
1. If it passes, the status becomes **SCREENING_PASSED**.
2. If it fails, the status becomes **HELD**.
3. If it fails because the destination is a sanctioned blockchain address, the status becomes **REJECTED** (final).
6. Transactions in **HELD** are subject to manual review:
1. If manually approved, the status changes to **SCREENING_PASSED**.
2. If rejected, the status becomes **REJECTED** (final).
7. After the screening is passed, the system assembles and signs the WITHDRAWAL. It becomes ready for blockchain submission. The state updates to **SIGNED**.
8. The signed transaction is submitted to the blockchain; once detected by monitoring logic, its status becomes **DETECTED**.
9. The respective blockchain processes the transaction and includes it in a new block. This blockchain transaction may contain errors if it is unsuccessful.
1. If execution fails (irreversible), status becomes FAILED (final). This action is irreversible.
2. If execution succeeds, status becomes **UNCONFIRMED**. The transaction is monitored until it reaches the required confirmation threshold. For more information, see [confirmation threshold](/layer1/references/supported-assets-and-protocols).
10. After the required number of confirmations is reached, the status updates to **CONFIRMED**.
11. The reserved funds are released and available balance is updated to reflect the irrevocable blockchain fund movement. The WITHDRAWAL is now finalised and state is set to **SUCCESS**.
### Withdrawal Status
A Withdrawal goes through a series of statuses, with each status denoting a specific process has occurred:
| **Status** | **Description** |
| :------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`CREATED`** | The withdrawal request is first registered with the created status. |
| **`FUNDS_NEEDED`** | The withdrawal is paused if there are insufficient funds within the asset pool to action the withdrawal. The transaction automatically progresses once matching funds are detected, or is rejected after 24 hours. |
| **`FUNDS_REQUESTED`** | A top up of funds has been requested to ensure enough balance is present to process withdrawal. |
| **`REJECTED`** | The withdrawal is rejected either if there are insufficient funds within the asset pool to action the withdrawal or as a result of screening. |
| **`FUNDS_RESERVED`** | The withdrawal has moved funds from the available balance to the reserved balance. |
| **`APPROVAL_REQUESTED`** | The withdrawal requires approval and is pending review by approvers. |
| **`SCREENING_REQUESTED`** | The withdrawal has moved into the screening phase and requires screening. |
| **`HELD`** | The withdrawal has been held to be manually reviewed. |
| **`SCREENING_PASSED`** | The withdrawal has passed screening, manual or automatic. |
| **`SIGNED`** | The withdrawal is signed and submitted to the blockchain. |
| **`DETECTED`** | The withdrawal has been detected and distributed on the blockchain, but does not yet have any confirmations. |
| **`FAILED`** | The withdrawal failed due to an on-chain error that occurred during execution, which could be due to multiple underlying reasons. |
| **`UNCONFIRMED`** | The withdrawal has had one or more blockchain confirmations, but has not yet reached the required confirmation limit. |
| **`CONFIRMED`** | The withdrawal has reached the required confirmation limit and is not fully confirmed on the blockchain. |
| **`SUCCESS`** | The withdrawal is successful, and the balance of the asset pool is updated to reflect this. |
| **`BLOCKED`** | An unexpected failure has occurred. Withdrawal must be checked by support before retrying. |
| **`REPLACED`** | An unexpected failure has occurred and manual action was required by the Layer1 team. The replaced withdrawal will have the same `reference` value. |
---
## Screen transactions
## Supported screening providers
Layer1 integrates with leading blockchain intelligence platforms to help identify and mitigate exposure to high-risk counterparties. Today, the platform supports the following screening providers:
| Provider | Description | Coverage Areas | Integration Type |
| --------------- | --------------------------------------------------------------------------- | --------------------------------------------------------- | ---------------- |
| **Elliptic** | Blockchain analytics firm offering real-time risk scoring and wallet checks | Wallet screening, transaction risk, entity categorization | API Key (REST) |
| **Chainalysis** | Cryptocurrency compliance and investigation platform | Wallet and address screening, KYT | API Key (REST) |
## Add a screening provider
To add a screening provider:
1. Go to **Integrations** > **Connected providers** and click **Add Provider**.
2. Select **Screening**.
3. Use the **Provider** dropdown to select your provider, then complete the following fields:
- **Display name**: Enter the name you want to give this screening provider on Layer1.
- **Token**: Enter the API token from the screening provider you are connecting.
4. Click **Add provider**.
It can take up to 24 hours to initialise your connection with your screening provider.
## Configure screening settings
:::note
To update screening settings you must have the role `compliance:admin`
:::
To update screening settings:
1. Go to **Digital Assets** > **Screening**.
2. Select **Settings**. Here you can view your connected provider and edit your settings.
3. To disable a screening provider or service (that is, KYT or Address Screening) select **Edit** screening services and toggle screening to disabled.
4. To edit your risk score configuration select **Edit** manual review scores. Use the dropdown to select the score at and above which transactions should require manual review before being processed.
---
## Stake TRX to reduce gas fees
When you send transactions on the TRON network—whether moving native TRX or TRC-20 tokens (for example, USDT)—gas fees are always paid in TRX and are always borne by the sender. In high-volume use cases, such as consolidations, frequent transfers, and payouts, these fees (which often range from $3 to $4 per transaction) can accumulate quickly.
TRON’s native staking product allows you to reduce or eliminate these fees by using Energy instead of spending TRX. Layer1 simplifies this process by managing staking, energy allocation, and monitoring on your behalf.
## Overview
By staking TRX, you unlock **Energy**, a resource that TRON automatically applies toward transaction fees (or gas). With Layer1’s built-in staking product, you no longer need to monitor Energy balances or make manual adjustments. Stake once, and the system applies Energy on your behalf to reduce or eliminate TRX-based fees.
### How Staking on TRON Works
#### Freeze (Stake) TRX to Receive Energy
- When you “freeze” (stake) TRX on TRON, you receive an allotment of Energy. More TRX staked means more Energy obtained. The exact Energy yield fluctuates based on overall network conditions (for example, total TRX staked by all users).
- Energy is consumed automatically by your transactions.
- Continuously, your staked TRX regenerates Energy. As long as Energy remains available, your transactions use Energy instead of TRX for gas.
- A small amount of TRX (0.345 TRX per transaction) is still required for Bandwidth. If Energy covers the gas portion, you’ll only pay this minimal Bandwidth fee in TRX.
- If your Energy balance is depleted, TRX will be spent for gas as usual to ensure no transaction fails for lack of fees.
#### Unstaking and lockup period
- When you unstake (unfreeze) TRX, those tokens enter a mandatory 14-day lockup.
- During the 14-day period, you cannot transfer or re-stake the unstaked TRX.
- After the lockup expires, the TRX funds are fully released back into your wallet.
### Why use Layer1 for TRON staking?
Layer1 abstracts away all staking complexity so your engineering team can focus on core product logic:
- Once you stake through Layer1, Energy is applied automatically to all eligible transactions, without any manual intervention. You never need to check or refill Energy.
- Layer1’s dashboard and webhooks provide real-time updates on:
- Staked amounts
- Unstaked amounts in the lock up period
- Delegated staked amounts for consolidation benefit
- After staking, payouts, consolidations, and other regular transactions automatically draw on Energy; no extra configuration is necessary.
- Layer1 handles all TRON staking mechanics. You only need to specify how much TRX to stake via a simple API call.
## Stake TRX
Below is the typical sequence you’ll see in Layer1 when staking TRX. All actions occur in your TRON master wallet, and Layer1 reflects changes in your asset balances.
| Asset | Transaction Type | Description |
| ------ | ---------------- | ---------------------------------------------------------------------------------------------------- |
| TRX | – | Your TRX balance in the master wallet before staking. |
| S-TRX | STAKE | TRX has been staked. Your “S-TRX” asset balance on the master wallet increases by the staked amount. |
To stake a specified amount of TRX via the Layer1 API, ensure your API key has the `crypto:operator` role and send the [`POST /digital/v1/staking-requests`](/l1-api/digital-assets/create-staking-transaction) request with the body parameters:
```json title="Body parameters"
{
"assetPoolId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"asset": "TRX",
"network": "TRON",
"type": "STAKE",
"reference": "myUniqueRef333",
"address": "TNx5eZNjpmKw8s1a3GB1VBH246uf9V6pfe",
"amount": "1000",
"metadata": {
"resourceType": "ENERGY"
}
}
```
Key points:
- Set `"asset": "TRX"` and `"type": "STAKE"`.
- `address` should be your TRON master wallet address.
- `amount` is the TRX quantity you wish to stake.
Layer1 sends a TRON transaction to freeze the TRX balance in the master wallet. Once the transaction is confirmed on-chain (**Layer1 returns a successful API response or a webhook notification**), your wallet’s S-TRX balance reflects the newly staked TRX. You start earning Energy automatically; no further steps needed.
## Unstake TRX
When you decide to unstake, Layer1 manages the transitions between asset types and the lockup timeline:
| Asset | Transaction Type | Description |
| -------- | ---------------- | ------------------------------------------------------------------------------------------------------------- |
| S-TRX | STAKE | Represents your currently staked TRX in the master wallet. |
| U-TRX | UNSTAKE | When you request to unstake, TRX moves from S-TRX to U-TRX, indicating it is now in the 14-day lockup period. |
| TRX | RELEASE | After the lockup expires, U-TRX automatically converts back to spendable TRX in your master wallet. |
To unstake a specified amount of TRX via the Layer1 API, ensure your API key has the `crypto:operator`role and send the [`POST /digital/v1/staking-requests`](/l1-api/digital-assets/create-staking-transaction) request with the body parameters:
```json title="Body parameters"
{
"assetPoolId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"asset": "S-TRX",
"network": "TRON",
"type": "UNSTAKE",
"reference": "myUniqueRef333",
"address": "TNx5eZNjpmKw8s1a3GB1VBH246uf9V6pfe",
"amount": "1000",
"metadata": {
"resourceType": "ENERGY"
}
}
```
Important details:
- Use `"asset": "S-TRX"` and `"type": "UNSTAKE"` to indicate you are freeing previously staked TRX.
After a successful request, a U-TRX asset balance is created in your wallet and locked for exactly 14 days.
Once the lockup period passes, U-TRX converts to normal TRX, which you can then transfer or re-stake. Layer1 UI and webhooks will notify you when your U-TRX becomes available.
## Consolidations
Staking TRX not only benefits payouts from your master wallet, it also helps reduce gas fees for consolidation transactions: moving funds from source wallets back into your master wallet.
This is enabled by temporarily delegating staked TRX from the master wallet to the source wallet when a deposit arrives:
| Asset | Transaction Type | Description |
| -------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| S-TRX | STAKE | Staked TRX resides on the master wallet. |
| D-TRX | DELEGATE | When a non-master (source) wallet receives a deposit, Layer1 temporarily delegates staked TRX from the master to the source wallet, this incurs a fee of ~0.279 TRX. Consolidation transaction triggers immediately, drawing on delegated Energy. |
| D-TRX | UNDELEGATE | Once the consolidation is complete, Layer1 automatically undelegates Energy back to the master wallet for future use. This incurs a fee of ~0.281 TRX. |
This process is entirely automated:
- Staked TRX is delegated only for the duration of the transaction.
- Afterwards, it's automatically undelegated.
- No additional configuration or manual setup is required.
All you need to do now is watch your TRON gas fees drop :chart_with_downwards_trend: - Simple!
If you have additional questions or need a deeper integration walkthrough, reach out to our Solutions team.
---
## Digital assets
The Digital Assets module powers the on-chain side of Layer1: crypto addresses, asset pools, deposits, withdrawals, staking, and transaction screening.
## Guides
- [Accept deposits to new addresses](/layer1/use-cases/digital-assets/accepting-deposits-to-new-addresses)
- [Create a withdrawal transaction request](/layer1/use-cases/digital-assets/creating-a-withdrawal-transaction-request)
- [Screening providers](/layer1/use-cases/digital-assets/screening-providers)
- [Staking](/layer1/use-cases/digital-assets/staking)
## Related API endpoints
- [Create asset pool](/l1-api/digital-assets/create-asset-pool)
- [Create address](/l1-api/digital-assets/create-address)
- [Create transaction](/l1-api/digital-assets/create-transaction)
- [Get transaction request by id](/l1-api/digital-assets/get-transaction-request-by-id)
- [Estimate transaction fee](/l1-api/digital-assets/estimate-transaction-fee)
- [Create staking transaction](/l1-api/digital-assets/create-staking-transaction)
---
## Create pay-in
To initiate a payment process, send the [`POST /api/v1/pay/summary`](/l1-api/crypto-payments/payment-create) request with the following body parameters. Remember to add the mandatory `payInDetails` object to return the cryptocurrency address required for the deposit on the correct blockchain.
```json title="Example Request"
{
"merchantId": "01968c39-3a1d-71af-8a8e-9c3ef3db3237",
"amount": 100,
"expiryMinutes": 60,
"currency": "USD",
"returnUrl": "https://yourwebsitename.com",
"reference": "6a4013c2-5fa9-4e2f-bef7-88c5570b6501",
"type": "IN",
"payInDetails": {
"currency": "USDT",
"protocol": "TRC20"
}
}
```
Find your `merchantId` in the Merchant's Portal, in the **Wallets** section. `currency` can be any supported local currency, not only a cryptocurrency. If you omit `expiryMinutes`, the payment expires after 1440 minutes. Set `payInDetails.protocol` when the `payInDetails.currency` is available on multiple protocols.
Upon successfully submitting the request, you'll receive a response like the following:
Response
```json title="Example Response"
{
"uuid": "fef4e99d-b41d-4efe-89fd-4fdf41f3f33f",
"merchantDisplayName": "Metallica Inc",
"merchantId": "01968c39-3a1d-71af-8a8e-9c3ef3db3237",
"dateCreated": 1704807605678,
"expiryDate": 1704808805678,
"quoteExpiryDate": 1704808806000,
"acceptanceExpiryDate": 1704807636000,
"quoteStatus": "ACCEPTED",
"reference": "a003cec4-abf5-42aa-baf0-43a1cc815536",
"type": "IN",
"subType": "merchantPayIn",
"status": "PENDING",
"displayCurrency": {
"currency": "USD",
"amount": 100,
"actual": 0
},
"walletCurrency": {
"currency": "EUR",
"amount": 92.74,
"actual": 0
},
"paidCurrency": {
"currency": "USDT",
"amount": 99.935377,
"actual": 0
},
"feeCurrency": {
"currency": "EUR",
"amount": 0.93,
"actual": 0
},
"displayRate": {
"base": "USDT",
"counter": "USD",
"rate": 0.998867803322
},
"exchangeRate": {
"base": "USDT",
"counter": "EUR",
"rate": 0.92635
},
"address": {
"address": "TTwyxkKzenGBhVX2TsjYLVFfApEQ3hPXGc",
"tag": null,
"protocol": "TRC20",
"uri": "TTwyxkKzenGBhVX2TsjYLVFfApEQ3hPXGc",
"alternatives": []
},
"returnUrl": "https://yourwebsitename.com",
"redirectUrl": "https://pay.sandbox.bvnk.com/payin?uuid=fef4e99d-b41d-4efe-89fd-4fdf41f3f33f",
"transactions": [],
"refund": null,
"refunds": []
}
```
Key takeaways include the following:
- `address.address` is the address to be shown to the end user so that they can make a deposit.
- `paidCurrency.amount` is the total amount of the relevant currency that needs to be paid by the end user to complete the payment, and should be shown to the end user.
- `uuid` is used to query the payment as needed.
This setup ensures a tailored payment experience, directly linking to the specified payment address. All Exchange rates are provided given that the payment currency is known at the time the payment is created.
After creating the payment, your next action is to display these details to your end-user for them to complete their payment. Once shown, wait for the payment to fire off a number of webhooks to let you know it has occurred. See [Receive pay-in notifications](/layer1/use-cases/merchant-payments/building-a-checkout-for-crypto-payments-1/listening-for-payment-in-webhooks-1).
---
## Receive pay-in notifications
Layer1 sends out a series of webhooks to keep you informed on any lifecycle event for the payment you have created.
## Webhook structure
The body of a webhook can be divided into three different sections:
- `source`. Specifies the product source of the webhook.
- `event`: Specifies an event that occurred within that source.
- `data`. Indicates relevant payment information sent with the webhook.
### Source types
For a pay-in, the source will always be `payment`.
### Event types
| Event | Description |
| :---------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `layer1:payment:checkout:status-change` | Happens twice, the status of the payment changes from `pending` to `processing`, or from `processing` to `complete` |
| `layer1:payment:checkout:transaction-confirmed` | The payment sent by the end-user has been confirmed on the blockchain and funds have been credited to your merchant account |
| `layer1:payment:checkout:transaction-detected` | The end-user has sent a payment and it has been detected on the blockchain but has not yet had the required number of block confirmations |
| `layer1:payment:checkout:transaction-late` | The end-user has sent funds to a payment that is already in an `expired`, `complete`, or `underpaid` state, or completed multiple payments to the same address |
| `layer1:payment:checkout:transaction-held` | A payment has been placed on hold for screening |
| `layer1:payment:checkout:refund-initiated` | An automatic refund was initiated |
| `layer1:payment:checkout:transaction-settled` | A payment has been settled |
### Data types
Data in each webhook is comprehensive. Note the most important fields here:
| Field | Description |
| :---------------- | :------------------------------------------------------------------------------------------------------------------------ |
| `uuid` | UUID of the linked payment. Remains the same across all webhook types for that payment |
| `type` | Type of payment, can be IN for deposit or OUT for withdrawal |
| `status` | Current status of the payment. The `EXPIRED`, `UNDERPAID` and `COMPLETE` statuses are final. |
| `displayCurrency` | Amounts in the selected display currency that the end user sees |
| `paidCurrency` | Amounts in the currency actually sent by the end user when fulfilling the payment |
| `walletCurrency` | Amounts in the settlement or wallet currency linked to the payment |
| `amount` | Amount requested when creating a payment |
| `actual` | Actual amount of money sent once the payment is confirmed on the blockchain |
| `transactions` | Array of transactions linked to the payment. If multiple payments are sent to the same address, they will all appear here |
## Common payment flow
Diagram of the Payment In lifecycle.
## Webhook sequence for each scenario
## Payment complete
When the end user successfully completes the payment within the allocated time frame and with the correct amount, you'll receive the following webhooks in order:
| Webhook Event Name | Description |
| --- | --- |
| **transactionDetected:** | Issued when the transaction first appears on the blockchain. This may follow the processing status webhook, depending on blockchain network speeds |
| **statusChanged - Processing:** | Dispatched when the payment status updates to `processing` after the transaction is recognized on the platform |
| **transactionConfirmed:** | Sent once the transaction achieves the requisite number of confirmations on the blockchain |
| **statusChanged - Complete:** | Issued when the payment status updates to `complete` following blockchain confirmation.This webhook delivers comprehensive payment details, including the final amounts sent (in the `actual` fields) and the transaction hash in the `transactions` array. |
## Payment expired
When an end user does not complete the payment, the payment expires. Only one webhook is sent out to let you know no transactions were detected within the given time frame.
| Webhook Event Name | Description |
| :-------------------------- | :------------------------------------------------------------------------------------------------------------- |
| **statusChanged - Expired** | Indicates the payment status has updated to `expired` due to elapsed expiry minutes without payment completion |
## Exception webhooks
### Underpaid
If the end user sends less cryptocurrency than required but within the expiry window, the following webhooks are triggered in order:
| Webhook Event Name | Description |
| --- | --- |
| **transactionDetected:** | Issued when the transaction first appears on the blockchain. This may follow the `processing` status webhook, depending on blockchain network speeds |
| **statusChanged - Processing:** | Dispatched when the payment status updates to `processing` after the transaction is recognized on the platform |
| **statusChanged - Underpaid:** | Sent once the transaction achieves the requisite number of confirmations on the blockchain.This webhook details the received amount (in the `actual` fields) allowing comparison with the expected amount (the `amount` fields) |
| **statusChanged - Complete:** | Marks the payment as underpaid after confirmation, noting the received amount is less than expected |
### Overpaid
If the end user sends more cryptocurrency than required but within the expiry window, the following webhooks are triggered in order:
| Webhook Event Name | Description |
| :------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **transactionDetected:** | Issued when the transaction first appears on the blockchain. This may follow the processing status webhook, depending on blockchain network speeds |
| **statusChanged - Processing:** | Dispatched when the payment status updates to `processing` after the transaction is recognized on the platform |
| **statusChanged - Underpaid:** | Sent once the transaction achieves the requisite number of confirmations on the blockchain |
| **statusChanged - Complete:** | Issued when the payment status updates to `complete` following blockchain confirmation. As there is more than required it completes the payment but will provide information on the additional amount sent |
Note the payments that exceed the expected amount and received within the expiry window. Their sequence is the same as for the complete payments. The notable distinction is in the `actual` field indicating a received `amount` that is higher than expected. Always compare the `actual` and `amount` fields in the final webhook to identify overpayments
### Late or Duplicate Payment
If the payment is detected or sent after the expiry webhook has been sent out, or if the end user sends an additional payment to the same payment links address, the following webhooks are triggered in order:
| Webhook Event Name | Description |
| --- | --- |
| **statusChanged - Expired:** | Indicates payment expiration after the lapse of the expiry window without payment |
| **transactionDetected:** | Detected transactions post-expiration are noted, possibly following the expiry notification |
| **transactionConfirmed:** | Confirms late transactions upon achieving blockchain confirmation criteria.The webhook also contains a comprehensive list of transactions |
| **transactionLate:** | Issued for confirmed transactions beyond the expiry limit or for duplicate payments to the same wallet address.Multiple payments to the same address trigger this webhook, which specifies details for each late or duplicate payment |
### Payment In Webhook Examples
The following is a comprehensive collection of the data sent for all of the webhooks mentioned earlier:
transactionDetected
```json title="transactionDetected"
{
"source": "payment",
"event": "transactionDetected",
"data": {
"uuid": "d993b0bc-dace-4742-81d8-6ae629dab063",
"merchantDisplayName": "ETH Merchant ",
"merchantId": "a7eddadd-fd9b-45fb-82a1-dc12eaa14cba",
"dateCreated": 1709895600000,
"expiryDate": 1709982000000,
"quoteExpiryDate": 1709982000000,
"acceptanceExpiryDate": 1709895667000,
"quoteStatus": "ACCEPTED",
"reference": "test_reference_in_0plkzH",
"type": "IN",
"subType": "merchantPayIn",
"status": "PENDING",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 0
},
"walletCurrency": {
"currency": "ETH",
"amount": 0.00276415,
"actual": 0
},
"paidCurrency": {
"currency": "ETH",
"amount": 0.00276415,
"actual": 0
},
"feeCurrency": {
"currency": "ETH",
"amount": 0.00002764,
"actual": 0
},
"displayRate": {
"base": "ETH",
"counter": "EUR",
"rate": 3617.748674999548
},
"exchangeRate": {
"base": "ETH",
"counter": "ETH",
"rate": 1
},
"address": {
"address": "0xc4d8b4b0e0601f49c88f41c4a9490f0d7f4ceed0",
"tag": null,
"protocol": "ETH",
"uri": "ethereum:0xc4d8b4b0e0601f49c88f41c4a9490f0d7f4ceed0?value=2.76415E+15",
"alternatives": []
},
"returnUrl": "https://www.your-url-here.com",
"redirectUrl": "https://pay.sandbox.bvnk.com/payin?uuid=d993b0bc-dace-4742-81d8-6ae629dab063&hpp=true",
"transactions": [
{
"dateCreated": 1709895679212,
"dateConfirmed": null,
"hash": "0x3d8ff17b4a2be304eff0ece0373f538f5e1a19e637652466c9ab15c599b6d91b",
"amount": 0,
"risk": null,
"networkFeeCurrency": "ETH",
"networkFeeAmount": null,
"sources": null,
"displayRate": {
"base": "ETH",
"counter": "EUR",
"rate": 3617.748674999548
},
"exchangeRate": {
"base": "ETH",
"counter": "ETH",
"rate": 1
},
"protocol": "ETH",
"isOnHold": false
}
],
"refund": null,
"refunds": []
}
}
```
transactionConfirmed
```json title="transactionConfirmed"
{
"source": "payment",
"event": "transactionConfirmed",
"data": {
"uuid": "d993b0bc-dace-4742-81d8-6ae629dab063",
"merchantDisplayName": "ETH Merchant ",
"merchantId": "a7eddadd-fd9b-45fb-82a1-dc12eaa14cba",
"dateCreated": 1709895600000,
"expiryDate": 1709982000000,
"quoteExpiryDate": 1709982000000,
"acceptanceExpiryDate": 1709895667000,
"quoteStatus": "ACCEPTED",
"reference": "test_reference_in_0plkzH",
"type": "IN",
"subType": "merchantPayIn",
"status": "PROCESSING",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 0
},
"walletCurrency": {
"currency": "ETH",
"amount": 0.00276415,
"actual": 0
},
"paidCurrency": {
"currency": "ETH",
"amount": 0.00276415,
"actual": 0.00276415
},
"feeCurrency": {
"currency": "ETH",
"amount": 0.00002764,
"actual": 0
},
"displayRate": {
"base": "ETH",
"counter": "EUR",
"rate": 3617.748674999548
},
"exchangeRate": {
"base": "ETH",
"counter": "ETH",
"rate": 1
},
"address": {
"address": "0xc4d8b4b0e0601f49c88f41c4a9490f0d7f4ceed0",
"tag": null,
"protocol": "ETH",
"uri": "ethereum:0xc4d8b4b0e0601f49c88f41c4a9490f0d7f4ceed0?value=2.76415E+15",
"alternatives": []
},
"returnUrl": "https://www.your-url-here.com",
"redirectUrl": "https://pay.sandbox.bvnk.com/payin?uuid=d993b0bc-dace-4742-81d8-6ae629dab063&hpp=true",
"transactions": [
{
"dateCreated": 1709895679000,
"dateConfirmed": 1709895776194,
"hash": "0x3d8ff17b4a2be304eff0ece0373f538f5e1a19e637652466c9ab15c599b6d91b",
"amount": 0.00276415,
"risk": {
"level": "LOW",
"resourceName": null,
"resourceCategory": null,
"alerts": []
},
"networkFeeCurrency": "ETH",
"networkFeeAmount": 0.000033940950855,
"sources": [
"0x84a4a239805d06c685219801b82bea7c76702214"
],
"displayRate": {
"base": "ETH",
"counter": "EUR",
"rate": 3617.748674999548
},
"exchangeRate": {
"base": "ETH",
"counter": "ETH",
"rate": 1
},
"protocol": "ETH",
"isOnHold": false
}
],
"refund": null,
"refunds": []
}
}
```
transactionLate
```json title="transactionLate"
{
"source": "payment",
"event": "transactionLate",
"data": {
"uuid": "1401c32a-f8c1-49d9-a24c-5ae81b0ea2b3",
"merchantDisplayName": "ETH Merchant ",
"merchantId": "a7eddadd-fd9b-45fb-82a1-dc12eaa14cba",
"dateCreated": 1709897205000,
"expiryDate": 1709897284000,
"quoteExpiryDate": 1709897265000,
"acceptanceExpiryDate": 1709897245000,
"quoteStatus": "ACCEPTED",
"reference": "test_reference_in_d1plQ7",
"type": "IN",
"subType": "merchantPayIn",
"status": "EXPIRED",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 10
},
"walletCurrency": {
"currency": "ETH",
"amount": 0.0027682,
"actual": 0.0027682
},
"paidCurrency": {
"currency": "ETH",
"amount": 0.0027682,
"actual": 0.0027682
},
"feeCurrency": {
"currency": "ETH",
"amount": 0.00002768,
"actual": 0.00002768
},
"displayRate": {
"base": "ETH",
"counter": "EUR",
"rate": 3612.455747417094
},
"exchangeRate": {
"base": "ETH",
"counter": "ETH",
"rate": 1
},
"address": {
"address": "0x8c12686c4277ae7b48a2cf19256cc29ba2184bf5",
"tag": null,
"protocol": "ETH",
"uri": "ethereum:0x8c12686c4277ae7b48a2cf19256cc29ba2184bf5?value=2.7682E+15",
"alternatives": []
},
"returnUrl": "https://www.your-url-here.com",
"redirectUrl": "https://pay.sandbox.bvnk.com/payin?uuid=1401c32a-f8c1-49d9-a24c-5ae81b0ea2b3&hpp=true",
"transactions": [
{
"dateCreated": 1709897305000,
"dateConfirmed": 1709897394000,
"hash": "0x8aa160b0b175624b5381f100d9b9ffbff199e65c6e53335b596aee926d831b42",
"amount": 0.0027682,
"risk": {
"level": "LOW",
"resourceName": null,
"resourceCategory": null,
"alerts": []
},
"networkFeeCurrency": "ETH",
"networkFeeAmount": 0.00005765,
"sources": [
"0x84a4a239805d06c685219801b82bea7c76702214"
],
"displayRate": {
"base": "ETH",
"counter": "EUR",
"rate": 3612.455747417094
},
"exchangeRate": {
"base": "ETH",
"counter": "ETH",
"rate": 1
},
"protocol": "ETH",
"isOnHold": false
}
],
"refund": null,
"refunds": []
}
}
```
transactionOnHold
```json title="transactionOnHold"
{
"source": "payment",
"event": "transactionOnHold",
"data": {
"uuid": "b078499c-0c6c-4e3f-8a32-66dca1d2676b",
"merchantDisplayName": "ETH Merchant ",
"merchantId": "a7eddadd-fd9b-45fb-82a1-dc12eaa14cba",
"dateCreated": 1709543074000,
"expiryDate": 1709715874000,
"quoteExpiryDate": 1709715874000,
"acceptanceExpiryDate": 1709543139000,
"quoteStatus": "ACCEPTED",
"reference": "REF958403",
"type": "IN",
"subType": "merchantPayIn",
"status": "PROCESSING",
"displayCurrency": {
"currency": "ETH",
"amount": 0.01,
"actual": 0
},
"walletCurrency": {
"currency": "ETH",
"amount": 0.01,
"actual": 0
},
"paidCurrency": {
"currency": "ETH",
"amount": 0.01,
"actual": 0
},
"feeCurrency": {
"currency": "ETH",
"amount": 0.0001,
"actual": 0
},
"displayRate": {
"base": "ETH",
"counter": "ETH",
"rate": 1
},
"exchangeRate": {
"base": "ETH",
"counter": "ETH",
"rate": 1
},
"address": {
"address": "0xc643e89d9499d1e888c0be5c89b0d49973115d8a",
"tag": null,
"protocol": "ETH",
"uri": "ethereum:0xc643e89d9499d1e888c0be5c89b0d49973115d8a?value=1E+16",
"alternatives": []
},
"returnUrl": "",
"redirectUrl": "https://pay.sandbox.bvnk.com/payin?uuid=b078499c-0c6c-4e3f-8a32-66dca1d2676b&hpp=true",
"transactions": [
{
"dateCreated": 1709543141000,
"dateConfirmed": null,
"hash": "0xfc403fb78ce3d05205c90ae91ddaf5f9760fd00ca15714b01951415ff3dbd172",
"amount": 0.01,
"risk": null,
"networkFeeCurrency": "ETH",
"networkFeeAmount": null,
"sources": null,
"displayRate": {
"base": "ETH",
"counter": "ETH",
"rate": 1
},
"exchangeRate": {
"base": "ETH",
"counter": "ETH",
"rate": 1
},
"protocol": "ETH",
"isOnHold": true
}
],
"refund": null,
"refunds": []
}
}
```
statusChanged - PROCESSING
```json title="statusChanged - PROCESSING"
{
"source": "payment",
"event": "statusChanged",
"data": {
"uuid": "d993b0bc-dace-4742-81d8-6ae629dab063",
"merchantDisplayName": "ETH Merchant ",
"merchantId": "a7eddadd-fd9b-45fb-82a1-dc12eaa14cba",
"dateCreated": 1709895600000,
"expiryDate": 1709982000000,
"quoteExpiryDate": 1709982000000,
"acceptanceExpiryDate": 1709895667000,
"quoteStatus": "ACCEPTED",
"reference": "test_reference_in_0plkzH",
"type": "IN",
"subType": "merchantPayIn",
"status": "PROCESSING",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 0
},
"walletCurrency": {
"currency": "ETH",
"amount": 0.00276415,
"actual": 0
},
"paidCurrency": {
"currency": "ETH",
"amount": 0.00276415,
"actual": 0
},
"feeCurrency": {
"currency": "ETH",
"amount": 0.00002764,
"actual": 0
},
"displayRate": {
"base": "ETH",
"counter": "EUR",
"rate": 3617.748674999548
},
"exchangeRate": {
"base": "ETH",
"counter": "ETH",
"rate": 1
},
"address": {
"address": "0xc4d8b4b0e0601f49c88f41c4a9490f0d7f4ceed0",
"tag": null,
"protocol": "ETH",
"uri": "ethereum:0xc4d8b4b0e0601f49c88f41c4a9490f0d7f4ceed0?value=2.76415E+15",
"alternatives": []
},
"returnUrl": "https://www.your-url-here.com",
"redirectUrl": "https://pay.sandbox.bvnk.com/payin?uuid=d993b0bc-dace-4742-81d8-6ae629dab063&hpp=true",
"transactions": [
{
"dateCreated": 1709895679212,
"dateConfirmed": null,
"hash": "0x3d8ff17b4a2be304eff0ece0373f538f5e1a19e637652466c9ab15c599b6d91b",
"amount": 0,
"risk": null,
"networkFeeCurrency": "ETH",
"networkFeeAmount": null,
"sources": null,
"displayRate": {
"base": "ETH",
"counter": "EUR",
"rate": 3617.748674999548
},
"exchangeRate": {
"base": "ETH",
"counter": "ETH",
"rate": 1
},
"protocol": "ETH",
"isOnHold": false
}
],
"refund": null,
"refunds": []
}
}
```
statusChanged - COMPLETE
```json title="statusChanged - COMPLETE"
{
"source": "payment",
"event": "statusChanged",
"data": {
"uuid": "d993b0bc-dace-4742-81d8-6ae629dab063",
"merchantDisplayName": "ETH Merchant ",
"merchantId": "a7eddadd-fd9b-45fb-82a1-dc12eaa14cba",
"dateCreated": 1709895600000,
"expiryDate": 1709982000000,
"quoteExpiryDate": 1709982000000,
"acceptanceExpiryDate": 1709895667000,
"quoteStatus": "ACCEPTED",
"reference": "test_reference_in_0plkzH",
"type": "IN",
"subType": "merchantPayIn",
"status": "COMPLETE",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 10
},
"walletCurrency": {
"currency": "ETH",
"amount": 0.00276415,
"actual": 0.00276415
},
"paidCurrency": {
"currency": "ETH",
"amount": 0.00276415,
"actual": 0.00276415
},
"feeCurrency": {
"currency": "ETH",
"amount": 0.00002764,
"actual": 0.00002764
},
"displayRate": {
"base": "ETH",
"counter": "EUR",
"rate": 3617.748674999548
},
"exchangeRate": {
"base": "ETH",
"counter": "ETH",
"rate": 1
},
"address": {
"address": "0xc4d8b4b0e0601f49c88f41c4a9490f0d7f4ceed0",
"tag": null,
"protocol": "ETH",
"uri": "ethereum:0xc4d8b4b0e0601f49c88f41c4a9490f0d7f4ceed0?value=2.76415E+15",
"alternatives": []
},
"returnUrl": "https://www.your-url-here.com",
"redirectUrl": "https://pay.sandbox.bvnk.com/payin?uuid=d993b0bc-dace-4742-81d8-6ae629dab063&hpp=true",
"transactions": [
{
"dateCreated": 1709895679000,
"dateConfirmed": 1709895776000,
"hash": "0x3d8ff17b4a2be304eff0ece0373f538f5e1a19e637652466c9ab15c599b6d91b",
"amount": 0.00276415,
"risk": {
"level": "LOW",
"resourceName": null,
"resourceCategory": null,
"alerts": []
},
"networkFeeCurrency": "ETH",
"networkFeeAmount": 0.00003394,
"sources": [
"0x84a4a239805d06c685219801b82bea7c76702214"
],
"displayRate": {
"base": "ETH",
"counter": "EUR",
"rate": 3617.748674999548
},
"exchangeRate": {
"base": "ETH",
"counter": "ETH",
"rate": 1
},
"protocol": "ETH",
"isOnHold": false
}
],
"refund": null,
"refunds": []
}
}
```
statusChanged - EXPIRED
```json title="statusChanged - EXPIRED"
{
"source": "payment",
"event": "statusChanged",
"data": {
"uuid": "c11b0f66-2e7f-4ff0-9963-e485511ae49f",
"merchantDisplayName": "ETH Merchant ",
"merchantId": "a7eddadd-fd9b-45fb-82a1-dc12eaa14cba",
"dateCreated": 1709896743000,
"expiryDate": 1709896862203,
"quoteExpiryDate": null,
"acceptanceExpiryDate": null,
"quoteStatus": "TEMPLATE",
"reference": "test_reference_in_ewmnnj",
"type": "IN",
"subType": "merchantPayIn",
"status": "EXPIRED",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 0
},
"walletCurrency": {
"currency": "ETH",
"amount": 0.00276456,
"actual": 0
},
"paidCurrency": {
"currency": null,
"amount": 0,
"actual": 0
},
"feeCurrency": {
"currency": "ETH",
"amount": 0,
"actual": 0
},
"displayRate": null,
"exchangeRate": null,
"address": null,
"returnUrl": "https://www.your-url-here.com",
"redirectUrl": "https://pay.sandbox.bvnk.com/payin?uuid=c11b0f66-2e7f-4ff0-9963-e485511ae49f&hpp=true",
"transactions": [],
"refund": null,
"refunds": []
}
}
```
statusChanged - UNDERPAID
```json title="statusChanged - UNDERPAID"
{
"source": "payment",
"event": "statusChanged",
"data": {
"uuid": "83e3287c-540e-4f43-8953-e5b2db646ca5",
"merchantDisplayName": "ETH Merchant ",
"merchantId": "a7eddadd-fd9b-45fb-82a1-dc12eaa14cba",
"dateCreated": 1709895979000,
"expiryDate": 1709982379000,
"quoteExpiryDate": 1709982379000,
"acceptanceExpiryDate": 1709896020000,
"quoteStatus": "ACCEPTED",
"reference": "test_reference_in_LGkyRO",
"type": "IN",
"subType": "merchantPayIn",
"status": "UNDERPAID",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 3.62
},
"walletCurrency": {
"currency": "ETH",
"amount": 0.00276601,
"actual": 0.001
},
"paidCurrency": {
"currency": "ETH",
"amount": 0.00276601,
"actual": 0.001
},
"feeCurrency": {
"currency": "ETH",
"amount": 0.00002766,
"actual": 0.00001
},
"displayRate": {
"base": "ETH",
"counter": "EUR",
"rate": 3615.315924382052
},
"exchangeRate": {
"base": "ETH",
"counter": "ETH",
"rate": 1
},
"address": {
"address": "0xe4828053f59d310ee50984d0cd84b622cc03b19f",
"tag": null,
"protocol": "ETH",
"uri": "ethereum:0xe4828053f59d310ee50984d0cd84b622cc03b19f?value=2.76601E+15",
"alternatives": []
},
"returnUrl": "https://www.your-url-here.com",
"redirectUrl": "https://pay.sandbox.bvnk.com/payin?uuid=83e3287c-540e-4f43-8953-e5b2db646ca5&hpp=true",
"transactions": [
{
"dateCreated": 1709896040000,
"dateConfirmed": 1709896133000,
"hash": "0xb8a33c20e4e0c6f747e87e1e632e9cdf22e62f0a99a18faefc7e327b85f8537d",
"amount": 0.001,
"risk": {
"level": "LOW",
"resourceName": null,
"resourceCategory": null,
"alerts": []
},
"networkFeeCurrency": "ETH",
"networkFeeAmount": 0.0000355,
"sources": [
"0x84a4a239805d06c685219801b82bea7c76702214"
],
"displayRate": {
"base": "ETH",
"counter": "EUR",
"rate": 3615.315924382052
},
"exchangeRate": {
"base": "ETH",
"counter": "ETH",
"rate": 1
},
"protocol": "ETH",
"isOnHold": false
}
],
"refund": null,
"refunds": []
}
}
```
statusChanged - CANCELLED
```json title="statusChanged - CANCELLED"
{
"source": "payment",
"event": "statusChanged",
"data": {
"uuid": "d993b0bc-dace-4742-81d8-6ae629dab063",
"merchantDisplayName": "ETH Merchant ",
"merchantId": "a7eddadd-fd9b-45fb-82a1-dc12eaa14cba",
"dateCreated": 1709895600000,
"expiryDate": 1709982000000,
"quoteExpiryDate": 1709982000000,
"acceptanceExpiryDate": 1709895667000,
"quoteStatus": "ACCEPTED",
"reference": "test_reference_in_0plkzH",
"type": "IN",
"subType": "merchantPayIn",
"status": "CANCELLED",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 10
},
"walletCurrency": {
"currency": "ETH",
"amount": 0.00276415,
"actual": 0.00276415
},
"paidCurrency": {
"currency": "ETH",
"amount": 0.00276415,
"actual": 0.00276415
},
"feeCurrency": {
"currency": "ETH",
"amount": 0.00002764,
"actual": 0.00002764
},
"displayRate": {
"base": "ETH",
"counter": "EUR",
"rate": 3617.748674999548
},
"exchangeRate": {
"base": "ETH",
"counter": "ETH",
"rate": 1
},
"address": {
"address": "0xc4d8b4b0e0601f49c88f41c4a9490f0d7f4ceed0",
"tag": null,
"protocol": "ETH",
"uri": "ethereum:0xc4d8b4b0e0601f49c88f41c4a9490f0d7f4ceed0?value=2.76415E+15",
"alternatives": []
},
"returnUrl": "https://www.your-url-here.com",
"redirectUrl": "https://pay.sandbox.bvnk.com/payin?uuid=d993b0bc-dace-4742-81d8-6ae629dab063&hpp=true",
"transactions": [
{
"dateCreated": 1709895679000,
"dateConfirmed": 1709895776000,
"hash": "0x3d8ff17b4a2be304eff0ece0373f538f5e1a19e637652466c9ab15c599b6d91b",
"amount": 0.00276415,
"risk": {
"level": "LOW",
"resourceName": null,
"resourceCategory": null,
"alerts": []
},
"networkFeeCurrency": "ETH",
"networkFeeAmount": 0.00003394,
"sources": [
"0x84a4a239805d06c685219801b82bea7c76702214"
],
"displayRate": {
"base": "ETH",
"counter": "EUR",
"rate": 3617.748674999548
},
"exchangeRate": {
"base": "ETH",
"counter": "ETH",
"rate": 1
},
"protocol": "ETH",
"isOnHold": false
}
],
"refund": null,
"refunds": []
}
}
```
---
## Accept digital asset payments
The guide covers the integration of payment links into your platform to accept crypto payments, detailing the following steps:
1. [Initiating a payment request and handling the response](/layer1/use-cases/merchant-payments/building-a-checkout-for-crypto-payments-1/creating-a-payment-1).
2. Guiding the end user to complete the payment.
3. [Setting up a webhook listener for payment notifications](/layer1/use-cases/merchant-payments/building-a-checkout-for-crypto-payments-1/listening-for-payment-in-webhooks-1).
4. [Managing payment exceptions and errors](/layer1/use-cases/merchant-payments/hosted-checkout-digital-asset-payments/payment-in-exception-scenarios-1).
## Payment flow
The payment proceeds through the following stages:
1. An end user makes a deposit request in your system, where you need to collect the following information:
- Amount for the deposit
- Cryptocurrency they want to pay with
- Protocol, if applicable
2. This information is then forwarded to BVNK as a Create Payment request.
3. The Create Payment endpoint returns an API response containing a crypto address matching the selected blockchain, along with other useful data. You can share this address with the end user, along with the total amount of cryptocurrency required to complete the payment.
4. The end user makes a payment to this address, which will then cause multiple related webhooks to be sent to your webhook listener as the payment goes through its lifecycle.
5. Once you receive the final `COMPLETE` webhook, you can poll the Get Payment endpoint to confirm the status and final paid amount or take the information from the webhook.
6. Update the end user's account accordingly with the confirmed payment amount.

## Payment lifecycle
A payment undergoes several status changes throughout its lifecycle:
| Status | Immutable? | Description |
| --- | --- | --- |
| `pending` | No | The initial status of your payment. Indicates that the payment remains in this state until it expires or a transaction linked to it is detected on the blockchain |
| `processing` | No | Indicates that the customer's payment has been detected on the blockchain. The payment remains in this state until funds are credited to your wallet.**Note**: Payments can still expire in this state if the transaction lacks sufficient gas or if the blockchain does not confirm the transaction within the expiry time |
| `complete` | Yes | Indicates that the funds have been received and credited to the merchant account in the chosen currency |
| `underpaid` | Yes | Indicates that the funds have been received and credited to the merchant account in the chosen currency, but the amount sent by the customer was less than the requested amount |
| `expired` | Yes | Indicates that the payment expired before the customer sent the funds |
---
## Charge merchant fees
With Layer1’s Fee Service, you have all the tools to charge your merchants for the payment processing services you provide. Setting this up correctly is a critical first step before processing any merchant payments.
## Key concepts
### Fee policies
Fee policies allow you to group and manage different types of fees tailored to your business requirements. Each policy can include a range of fees typically aligned with specific products, for example, `paymentIn`, `paymentOut`, and so on.
We recommend setting up the following:
- **Default Policy**: A fallback policy applied to all merchants unless overridden.
- **Merchant Policies**: Bespoke fee configurations for individual merchants.
:::note
The `DEFAULT` policy applies automatically without needing to link it to an account.
:::
### Linking Accounts to Policies
To apply a specific fee policy to a merchant account, the policy must be explicitly linked to the account. This is only necessary for merchant-specific policies. The `DEFAULT` policy is applied automatically.
## Add fee policies
The fee policies are added via Layer1 Portal. Your user must have the **layer1:role:fee:admin** role to manage merchant fees.
To add a fee policy, do the following:
1. Log in to Layer1 Portal, and in the menu go to **Merchant Payments** > **Fee policies**. This page has two tabs: **Policies**, where you create policies and add their fees, and **Fee types**. Linking policies to merchant accounts is a separate menu item, **Merchant Payments** > **Linked policies**.
Go to the **Fee types** tab. Fee types determine the structure of fees across payment flows and are used within your policies. **This step is handled once with guidance from our Solutions team**. These fees are the key building blocks of your policies, ensuring correct charges based on the flow type.
2. **Create a default fee policy**:
1. In **Fee policies**, go to the **Policies** tab, and click **Create policy**.
2. Enter a **Name** for the policy and specify the following parameters:
- Priority: `0`
- Enabled: yes
- Is default: yes
3. Click **Create**.
3. Repeat step 2 to create a **Merchant-Specific** policy with the following settings:
- Priority: `10` to ensure it overrides the default policy
- Enabled: yes
- Is default: no
4. Add fees to policies according to the products in use. On the **Policies** tab, click the policy, go to the **Policy fees** tab, and click **Add fee**. Select the **Fee type** and **Currency**, enter the fee amounts, and click **Create**:
- Fees can be **flat** or **percentage-based**. You can also set a **Minimum fee** and a **Max fee**.
- Set in **EUR** or **USD**. The Merchant Payment Engine handles any necessary currency conversion to the merchant's wallet currency.
5. Link merchant-specific policies to merchant accounts. There are two ways to do this:
- **From the policy**:
1. On the **Policies** tab, click the policy. The **Linked accounts** tab opens.
2. Click **Link an account**.
3. Choose the account to link from the menu.
- **From the account**:
1. On the main menu, go to **Merchant Payments** > **Linked policies** and select the required account.
2. Click **Link a policy to account**.
3. Select your policy from the menu.
Once you've completed these steps, you're ready to start processing merchant payments with the appropriate fees applied.
:::warning[Important]
Fee configuration directly impacts how merchants are charged. Always consult with the Layer1 **Solutions team** when setting up or updating fee structures to ensure accuracy and compliance.
:::
---
## Payment in
The Payment In flow allows merchant's end-users to select their digital asset of choice to pay with.
## Payment In Sequence Diagram

The general happy path will be discussed, with exception scenarios discussed later on in the guide:
1. The end user will request to make a deposit on your system, which will then be forward onto us in the form of a Create Payment request.
2. The Create Payment endpoint will return a redirectUrl, along with other useful data in the API response. End user can then be directed to this URL which hosts our payments page
3. The end user will select the digital asset and network in the dropdown that they want to make the payment with, and as this is now on our hosted page, the request is sent directly to us.
4. We generate an blockchain address to match the requested digital asset and network, and auto-update the Hosted Payments Page to display this newly created address.
5. The end user makes a payment to this address, which will then cause multiple related webhooks to be sent to your webhook listener as the payment goes through its lifecycle.
6. Once you receive the final `COMPLETE`webhook you can poll the Get Payment endpoint to confirm the status and final paid amount, or take the information from the webhook.
7. Update the end users account accordingly with the confirmed payment amount.
## Payment Lifecycle
A payment undergoes several status changes throughout its lifecycle, detailed as follows:
| Status | Immutable? | Description |
| --- | --- | --- |
| Pending | No | The initial status of your payment. It remains in this state until the payment either expires or a transaction linked to this payment is detected on the blockchain. |
| Processing | No | This status indicates that the customer's payment has been detected on the blockchain. The payment remains in this state until funds are credited to your wallet.Note: Payments can still expire in this state if the transaction lacks sufficient gas or if the blockchain does not confirm the transaction within the expiry time. |
| Complete | Yes | This status means that the funds have been received and credited to the merchant account in the chosen currency. |
| Underpaid | Yes | Indicates that the funds have been received and credited to the merchant account in the chosen currency, but the amount sent by the customer was less than the requested amount. |
| Expired | Yes | The payment expired before the customer sent the funds. |
## Initiating the payment in
To initiate a payment process, utilize the [Create Payment](/l1-api/crypto-payments/payment-create) endpoint.
Below are the details required for the create payment request. Set `type` to `IN`, and use the Merchant ID from the Merchant Details page as `merchantId`. If you omit `expiryMinutes`, the payment expires after 1440 minutes.
## Example Request
Here is how an example request might look:
```json title="Example Request"
{
"merchantId": "00a4af9d-ad4b-42d5-bec4-b7a8c90161fe",
"amount": 100,
"expiryMinutes": 60,
"currency": "USD",
"returnUrl": "https://yourwebsitename.com",
"reference": "6a4013c2-5fa9-4e2f-bef7-88c5570b6501",
"type": "IN"
}
```
## Example Response
Upon successfully submitting the request, you'll receive a response like the following:
Response
```json title="Example Response"
{
"uuid": "fef4e99d-b41d-4efe-89fd-4fdf41f3f33f",
"merchantDisplayName": "Metallica Inc",
"merchantId": "00a4af9d-ad4b-42d5-bec4-b7a8c90161fe",
"dateCreated": 1704807605678,
"expiryDate": 1704808805678,
"quoteExpiryDate": 1704808806000,
"acceptanceExpiryDate": 1704807636000,
"quoteStatus": "ACCEPTED",
"reference": "a003cec4-abf5-42aa-baf0-43a1cc815536",
"type": "IN",
"subType": "merchantPayIn",
"status": "PENDING",
"displayCurrency": {
"currency": "USD",
"amount": 100,
"actual": 0
},
"walletCurrency": {
"currency": "EUR",
"amount": 100,
"actual": 0
},
"paidCurrency": {
"currency": null,
"amount": 0,
"actual": 0
},
"feeCurrency": {
"currency": "EUR",
"amount": 1,
"actual": 0
},
"displayRate": null,
"exchangeRate": null,
"address": null,
"returnUrl": "https://yourwebsitename.com",
"redirectUrl": "https://pay.sandbox.layer1.com/payin?uuid=fef4e99d-b41d-4efe-89fd-4fdf41f3f33f",
"transactions": [],
"refund": null,
"refunds": []
}
```
At this juncture, it's crucial to note the `uuid` and `redirectUrl`.
The uuid is used for querying the payment status, and the redirectUrl directs the end-user to the Hosted Payments Page for digital asset selection.
Note that as the payment currency is not known at this stage, exchange rates and paid currency values are `null`
:::note
If you already know the digital asset the end-user will pay with, refer to the next guide for creating a tailored payment request.
:::
---
## Directing the end-user to pay
Following the successful creation of a payment in the previous step, where you received payment details from the API, this step focuses on utilizing the redirectUrl.
After creating the payment, your next action is to direct your end-user to the Hosted Payments Page for them to complete their payment. This redirection can be achieved either by directly redirecting the user or by embedding the URL within an iframe on your site.
Once redirected, your role is simply to wait.
The Hosted Payments Page, encountered by your end-user, is segmented into four distinct sections:
1. Digital assets selection,
2. Blockchain address and QR code,
3. Pending payment confirmation, and
4. Payment completion.
Each of these sections is illustrated below:
1. **Digital asset and network selection:** Here, users are prompted to select a Digital Asset (for example, BTC, USDT, ETH) and network (for example, TRON, BASE) to fulfill a payment request, in this case, 10 EUR from a merchant.

2. **Blockchain address and QR Code Screen:** This screen allows end users to scan a QR code to open their wallet on their phone or copy wallet details manually.

3. **Pending Payment Screen:** As soon as the transaction is visible on the blockchain, the user will be notified that the payment is in progress. Concurrently, the payment status updates to `processing` in the API.

4. **Payment Completion Screen:** Finally, when the payment has been fully processed and settled, the user is informed that the payment is complete, with the funds now available in the merchant's account.

:::note[Digital assets supported]
Only the digital assets that are available on the hosted payments page can be used by end-users to complete the payment.
:::
With the payment journey now complete for the end user, Layer1 proceeds to finalize and update the payment status on your portal and dispatches webhook notifications for you to update your system accordingly.
Next, see [Listening for payment in webhooks](/layer1/use-cases/merchant-payments/hosted-checkout-digital-asset-payments/listening-for-payment-in-webhooks).
---
## Directing the end-user to payout
Following the successful creation of a payment in the previous step, where you received payment details from the API, this step focuses on utilizing the redirectUrl.
After creating the payment, your next action is to direct your end-user to the Hosted Payments Page for them to complete their payout. This redirection can be achieved either by directly redirecting the user or by embedding the URL within an iframe on your site.
Once redirected, your role is simply to wait.
The Hosted Payments Page, encountered by your end-user, is segmented into four distinct sections:
1. Digital assets selection, and Input receiving address and validation
2. Payment completion.
Each of these sections is illustrated below:
1. **Digital asset and network selection:** Here, users are prompted to select a Digital Asset (for example, BTC, USDT, ETH) and network (for example, TRON, BASE) to receive, in this case, 10 EUR from the merchant.
Select Digital Asset and Network, input wallet address for validation, and confirm.
2. **Payment Completion Screen:** When the payment has been fully processed, the user is informed that the payment is complete, with the funds now submitted on the blockchain.
End-user gets confirmed on transaction being submitted to the blockchain.
:::note[Digital assets supported]
Only the digital assets that are available on the hosted payments page can be used by end-users to complete the payment.
:::
With the payment journey now complete for the end user, Layer1 proceeds to finalize and update the payment status on your portal and dispatches webhook notifications for you to update your system accordingly.
Next, see [(Optional) Pre-selecting the digital asset to receive](/layer1/use-cases/merchant-payments/hosted-checkout-digital-asset-payments/optional-preselecting-digital-asset-to-receive) and [Listening for payout webhooks](/layer1/use-cases/merchant-payments/hosted-checkout-digital-asset-payments/listening-for-payout-webhooks).
---
## Listening for payment in webhooks
Layer1 sends out a series of webhooks to keep you informed on any lifecycle event for the payment you have created.
The body of a webhook can be divided into two main different sections, `event` and `data`. The event will be what event has occurred within the product source of the webhook, and data will be all of the relevant payment information sent with the webhook.
### Event types:
| Event | Description |
| :---------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `layer1:payment:checkout:status-change` | Happens twice, the status of the payment has changed, either from `pending` to `processing`, `pending` to `expired` , `processing` to `complete`, or `processing` to `expired` |
| `layer1:payment:checkout:transaction-detected` | The end-user has sent a payment and it has been detected on the blockchain but has not yet had the required number of block confirmations. |
| `layer1:payment:checkout:transaction-confirmed` | The payment the end-user sent has been confirmed on the blockchain and funds have been credited to your merchant account. |
| `layer1:payment:checkout:transaction-held` | A payment has been placed on hold for screening. |
| `layer1:payment:checkout:transaction-late` | The end-user has sent funds to a payment that is already in an expired, complete, or underpaid state. |
### Data types
Data in each webhook is comprehensive, but we will highlight some of the most important fields here:
| Field | Description |
| :---------------- | :----------------------------------------------------------------------------------------------------------------------------- |
| `uuid` | UUID of the linked payment, will remain the same across all webhook types for that payment. |
| `type` | The type of payment, can be IN for deposit or OUT for withdrawal |
| `status` | The current status of the payment. `EXPIRED`, `UNDERPAID` and `COMPLETE` statuses are all final. |
| `displayCurrency` | The amounts in the selected display currency that the end user will see. |
| `paidCurrency` | The amounts in the currency actually sent by the end user when fulfilling the payment. |
| `walletCurrency` | The amounts in the settlement or wallet currency linked to the payment. |
| `amount` | The amount requested when creating a payment. |
| `actual` | The actual amount of money sent once the payment is confirmed on the blockchain. |
| `transactions` | The array of transactions linked to the payment, if multiple payments are sent to the same address, they will all appear here. |
## Payment Flow Diagram and Webhooks Sent

## Webhook Sequence for Each Scenario
### Payment Complete Webhooks
Upon the end user successfully completing the payment within the allocated time frame and with the correct amount, you'll receive the following webhooks:
1. **transaction-detected:** Issued when the transaction first appears on the blockchain. This may follow the processing status webhook, depending on blockchain network speeds.
2. **status-change - Processing:** Dispatched when the payment status updates to processing after the transaction is recognized on the platform.
3. **transaction-confirmed:** Sent once the transaction achieves the requisite number of confirmations on the blockchain.
4. **status-change - Complete:** Issued when the payment status updates to complete following blockchain confirmation.
The status-change - Complete webhook delivers comprehensive payment details, including the final amounts sent (`actual` fields) and the transaction hash within the `transactions` array.
### Payment Underpaid Webhooks
Upon the end user successfully completing the payment within the allocated time frame and amount received less than expected, you'll receive the following webhooks:
1. **transaction-detected:** Issued when the transaction first appears on the blockchain. This may follow the processing status webhook, depending on blockchain network speeds.
2. **status-change - Processing:** Dispatched when the payment status updates to processing after the transaction is recognized on the platform.
3. **transaction-confirmed:** Sent once the transaction achieves the requisite number of confirmations on the blockchain.
4. **status-change - Underpaid:** Issued when the payment status updates to complete following blockchain confirmation.
The status-change - Underpaid webhook delivers comprehensive payment details, including the final amounts sent (`actual` fields) and the transaction hash within the `transactions` array.
### Payment Expired Webhooks
Should the end user fail to complete the payment within the designated time, you'll receive:
1. **status-change - Expired** indicates the payment status has updated to expired due to elapsed expiry minutes without payment completion
2. **transaction-late** indicates the payment has been received after the payment is in the expire (or complete) state
When an end user does not complete the payment, the payment expires and the expired webhook is sent out to let you know no transactions were detected within the given time frame.
### Payment In Webhook Examples:
transaction-detected
```json title="transaction-detected"
{
"event": "layer1:payment:checkout:transaction-detected",
"eventId": "01977e6e-56a5-7100-bf7c-547deeb17314",
"timestamp": "2025-06-17T15:07:29.829291749Z",
"data": {
"uuid": "4de6afee-7446-4da2-8f00-46debbf67560",
"merchantDisplayName": "USDT wallet",
"merchantId": "ce3fe53f-2461-46d7-9e8a-0e58003cd358",
"dateCreated": 1750172749364,
"expiryDate": 1750173949364,
"quoteExpiryDate": 1750173949364,
"acceptanceExpiryDate": 1750172830290,
"quoteStatus": "ACCEPTED",
"reference": "7ed966de-ed04-4107-b08f-ccfc4345a9f9",
"type": "IN",
"subType": "merchantPayIn",
"status": "PENDING",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 0
},
"walletCurrency": {
"currency": "USDT",
"amount": 11.572366,
"actual": 0
},
"paidCurrency": {
"currency": "USDT",
"amount": 11.572366,
"actual": 0
},
"feeCurrency": {
"currency": "USDT",
"amount": 0.381888,
"actual": 0
},
"networkFeeCurrency": {
"currency": null,
"amount": 0,
"actual": 0
},
"displayRate": {
"base": "USDT",
"counter": "EUR",
"rate": 0.864127525866
},
"exchangeRate": {
"base": "USDT",
"counter": "USDT",
"rate": 1
},
"address": {
"address": "2Y3CHaT3JYFC5yRD9XJzNMRHjbdMfXDnzYHRWyLf8gnk",
"tag": null,
"protocol": "SOLANA",
"uri": "2Y3CHaT3JYFC5yRD9XJzNMRHjbdMfXDnzYHRWyLf8gnk",
"alternatives": [
{
"address": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"tag": null,
"protocol": "POLYGON",
"uri": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"alternatives": []
},
{
"address": "TD7LgdxRQn4X27WALU8J68HgUNAhu2XLT8",
"tag": null,
"protocol": "TRON",
"uri": "TD7LgdxRQn4X27WALU8J68HgUNAhu2XLT8",
"alternatives": []
},
{
"address": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"tag": null,
"protocol": "ETHEREUM",
"uri": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"alternatives": []
},
{
"address": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"tag": null,
"protocol": "BINANCE",
"uri": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"alternatives": []
}
]
},
"returnUrl": "https://yourwebsitename.com",
"redirectUrl": "https://pay.sandbox.layer1.com/payin/4de6afee-7446-4da2-8f00-46debbf67560",
"transactions": [
{
"dateCreated": 1750172849818,
"dateConfirmed": null,
"hash": "0x4e804daaae2f53460ee9821e86a3acb3be740664b25006f1717e5ad82fcdf731",
"amount": 11.572366,
"networkFeeCurrency": "USDT",
"networkFeeAmount": null,
"sources": [
"0x84a4a239805d06c685219801b82bea7c76702214",
"0x84a4a239805d06c685219801b82bea7c76702214"
],
"displayRate": {
"base": "USDT",
"counter": "EUR",
"rate": 0.864127525866
},
"exchangeRate": {
"base": "USDT",
"counter": "USDT",
"rate": 1
},
"protocol": "ERC20",
"isOnHold": false
}
],
"refund": null,
"refunds": [],
"currencyOptions": null,
"flow": null,
"twoStep": false,
"metadata": {},
"customerId": null,
"pegged": false
}
}
```
status-change - PROCESSING
```json title="status-change - PROCESSING"
{
"event": "layer1:payment:checkout:status-change",
"eventId": "01977e6e-56ab-70e0-849a-538e375d49b8",
"timestamp": "2025-06-17T15:07:29.835356203Z",
"data": {
"uuid": "4de6afee-7446-4da2-8f00-46debbf67560",
"merchantDisplayName": "USDT wallet",
"merchantId": "ce3fe53f-2461-46d7-9e8a-0e58003cd358",
"dateCreated": 1750172749364,
"expiryDate": 1750173949364,
"quoteExpiryDate": 1750173949364,
"acceptanceExpiryDate": 1750172830290,
"quoteStatus": "ACCEPTED",
"reference": "7ed966de-ed04-4107-b08f-ccfc4345a9f9",
"type": "IN",
"subType": "merchantPayIn",
"status": "PROCESSING",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 0
},
"walletCurrency": {
"currency": "USDT",
"amount": 11.572366,
"actual": 0
},
"paidCurrency": {
"currency": "USDT",
"amount": 11.572366,
"actual": 0
},
"feeCurrency": {
"currency": "USDT",
"amount": 0.381888,
"actual": 0
},
"networkFeeCurrency": {
"currency": null,
"amount": 0,
"actual": 0
},
"displayRate": {
"base": "USDT",
"counter": "EUR",
"rate": 0.864127525866
},
"exchangeRate": {
"base": "USDT",
"counter": "USDT",
"rate": 1
},
"address": {
"address": "2Y3CHaT3JYFC5yRD9XJzNMRHjbdMfXDnzYHRWyLf8gnk",
"tag": null,
"protocol": "SOLANA",
"uri": "2Y3CHaT3JYFC5yRD9XJzNMRHjbdMfXDnzYHRWyLf8gnk",
"alternatives": [
{
"address": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"tag": null,
"protocol": "POLYGON",
"uri": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"alternatives": []
},
{
"address": "TD7LgdxRQn4X27WALU8J68HgUNAhu2XLT8",
"tag": null,
"protocol": "TRON",
"uri": "TD7LgdxRQn4X27WALU8J68HgUNAhu2XLT8",
"alternatives": []
},
{
"address": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"tag": null,
"protocol": "ETHEREUM",
"uri": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"alternatives": []
},
{
"address": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"tag": null,
"protocol": "BINANCE",
"uri": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"alternatives": []
}
]
},
"returnUrl": "https://yourwebsitename.com",
"redirectUrl": "https://pay.sandbox.layer1.com/payin/4de6afee-7446-4da2-8f00-46debbf67560",
"transactions": [
{
"dateCreated": 1750172849818,
"dateConfirmed": null,
"hash": "0x4e804daaae2f53460ee9821e86a3acb3be740664b25006f1717e5ad82fcdf731",
"amount": 11.572366,
"networkFeeCurrency": "USDT",
"networkFeeAmount": null,
"sources": [
"0x84a4a239805d06c685219801b82bea7c76702214",
"0x84a4a239805d06c685219801b82bea7c76702214"
],
"displayRate": {
"base": "USDT",
"counter": "EUR",
"rate": 0.864127525866
},
"exchangeRate": {
"base": "USDT",
"counter": "USDT",
"rate": 1
},
"protocol": "ERC20",
"isOnHold": false
}
],
"refund": null,
"refunds": [],
"currencyOptions": null,
"flow": null,
"twoStep": false,
"metadata": {},
"customerId": null,
"pegged": false
}
}
```
transaction-confirmed
```json title="transaction-confirmed"
{
"event": "layer1:payment:checkout:transaction-confirmed",
"eventId": "01977e70-5b79-7bd7-90ca-35c0942b75e3",
"timestamp": "2025-06-17T15:09:42.137997272Z",
"data": {
"uuid": "4de6afee-7446-4da2-8f00-46debbf67560",
"merchantDisplayName": "USDT wallet",
"merchantId": "ce3fe53f-2461-46d7-9e8a-0e58003cd358",
"dateCreated": 1750172749364,
"expiryDate": 1750173949364,
"quoteExpiryDate": 1750173949364,
"acceptanceExpiryDate": 1750172830290,
"quoteStatus": "ACCEPTED",
"reference": "7ed966de-ed04-4107-b08f-ccfc4345a9f9",
"type": "IN",
"subType": "merchantPayIn",
"status": "PROCESSING",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 0
},
"walletCurrency": {
"currency": "USDT",
"amount": 11.572366,
"actual": 0
},
"paidCurrency": {
"currency": "USDT",
"amount": 11.572366,
"actual": 11.572366
},
"feeCurrency": {
"currency": "USDT",
"amount": 0.381888,
"actual": 0
},
"networkFeeCurrency": {
"currency": null,
"amount": 0,
"actual": 0
},
"displayRate": {
"base": "USDT",
"counter": "EUR",
"rate": 0.864127525866
},
"exchangeRate": {
"base": "USDT",
"counter": "USDT",
"rate": 1
},
"address": {
"address": "2Y3CHaT3JYFC5yRD9XJzNMRHjbdMfXDnzYHRWyLf8gnk",
"tag": null,
"protocol": "SOLANA",
"uri": "2Y3CHaT3JYFC5yRD9XJzNMRHjbdMfXDnzYHRWyLf8gnk",
"alternatives": [
{
"address": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"tag": null,
"protocol": "POLYGON",
"uri": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"alternatives": []
},
{
"address": "TD7LgdxRQn4X27WALU8J68HgUNAhu2XLT8",
"tag": null,
"protocol": "TRON",
"uri": "TD7LgdxRQn4X27WALU8J68HgUNAhu2XLT8",
"alternatives": []
},
{
"address": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"tag": null,
"protocol": "ETHEREUM",
"uri": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"alternatives": []
},
{
"address": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"tag": null,
"protocol": "BINANCE",
"uri": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"alternatives": []
}
]
},
"returnUrl": "https://yourwebsitename.com",
"redirectUrl": "https://pay.sandbox.layer1.com/payin/4de6afee-7446-4da2-8f00-46debbf67560",
"transactions": [
{
"dateCreated": 1750172849818,
"dateConfirmed": 1750172982102,
"hash": "0x4e804daaae2f53460ee9821e86a3acb3be740664b25006f1717e5ad82fcdf731",
"amount": 11.572366,
"networkFeeCurrency": "USDT",
"networkFeeAmount": null,
"sources": [
"0x84a4a239805d06c685219801b82bea7c76702214",
"0x84a4a239805d06c685219801b82bea7c76702214"
],
"displayRate": {
"base": "USDT",
"counter": "EUR",
"rate": 0.864127525866
},
"exchangeRate": {
"base": "USDT",
"counter": "USDT",
"rate": 1
},
"protocol": "ERC20",
"isOnHold": false
}
],
"refund": null,
"refunds": [],
"currencyOptions": null,
"flow": null,
"twoStep": false,
"metadata": {},
"customerId": null,
"pegged": false
}
}
```
status-change - COMPLETE
```json title="status-change - COMPLETE"
{
"event": "layer1:payment:checkout:status-change",
"eventId": "01977e70-60b4-7f50-8b17-da09bc38f637",
"timestamp": "2025-06-17T15:09:43.476149964Z",
"data": {
"uuid": "4de6afee-7446-4da2-8f00-46debbf67560",
"merchantDisplayName": "USDT wallet",
"merchantId": "ce3fe53f-2461-46d7-9e8a-0e58003cd358",
"dateCreated": 1750172749364,
"expiryDate": 1750173949364,
"quoteExpiryDate": 1750173949364,
"acceptanceExpiryDate": 1750172830290,
"quoteStatus": "ACCEPTED",
"reference": "7ed966de-ed04-4107-b08f-ccfc4345a9f9",
"type": "IN",
"subType": "merchantPayIn",
"status": "COMPLETE",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 10
},
"walletCurrency": {
"currency": "USDT",
"amount": 11.572366,
"actual": 11.572366
},
"paidCurrency": {
"currency": "USDT",
"amount": 11.572366,
"actual": 11.572366
},
"feeCurrency": {
"currency": "USDT",
"amount": 0.381888,
"actual": 0.381888
},
"networkFeeCurrency": {
"currency": null,
"amount": 0,
"actual": 0
},
"displayRate": {
"base": "USDT",
"counter": "EUR",
"rate": 0.864127525866
},
"exchangeRate": {
"base": "USDT",
"counter": "USDT",
"rate": 1
},
"address": {
"address": "2Y3CHaT3JYFC5yRD9XJzNMRHjbdMfXDnzYHRWyLf8gnk",
"tag": null,
"protocol": "SOLANA",
"uri": "2Y3CHaT3JYFC5yRD9XJzNMRHjbdMfXDnzYHRWyLf8gnk",
"alternatives": [
{
"address": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"tag": null,
"protocol": "POLYGON",
"uri": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"alternatives": []
},
{
"address": "TD7LgdxRQn4X27WALU8J68HgUNAhu2XLT8",
"tag": null,
"protocol": "TRON",
"uri": "TD7LgdxRQn4X27WALU8J68HgUNAhu2XLT8",
"alternatives": []
},
{
"address": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"tag": null,
"protocol": "ETHEREUM",
"uri": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"alternatives": []
},
{
"address": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"tag": null,
"protocol": "BINANCE",
"uri": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"alternatives": []
}
]
},
"returnUrl": "https://yourwebsitename.com",
"redirectUrl": "https://pay.sandbox.layer1.com/payin/4de6afee-7446-4da2-8f00-46debbf67560",
"transactions": [
{
"dateCreated": 1750172849818,
"dateConfirmed": 1750172982102,
"hash": "0x4e804daaae2f53460ee9821e86a3acb3be740664b25006f1717e5ad82fcdf731",
"amount": 11.572366,
"networkFeeCurrency": "USDT",
"networkFeeAmount": null,
"sources": [
"0x84a4a239805d06c685219801b82bea7c76702214",
"0x84a4a239805d06c685219801b82bea7c76702214"
],
"displayRate": {
"base": "USDT",
"counter": "EUR",
"rate": 0.864127525866
},
"exchangeRate": {
"base": "USDT",
"counter": "USDT",
"rate": 1
},
"protocol": "ERC20",
"isOnHold": false
}
],
"refund": null,
"refunds": [],
"currencyOptions": null,
"flow": null,
"twoStep": false,
"metadata": {},
"customerId": null,
"pegged": false
}
}
```
status-change - UNDERPAID
```json title="status-change - UNDERPAID"
{
"event": "layer1:payment:checkout:status-change",
"eventId": "01977e70-60b4-7f50-8b17-da09bc38f637",
"timestamp": "2025-06-17T15:09:43.476149964Z",
"data": {
"uuid": "4de6afee-7446-4da2-8f00-46debbf67560",
"merchantDisplayName": "USDT wallet",
"merchantId": "ce3fe53f-2461-46d7-9e8a-0e58003cd358",
"dateCreated": 1750172749364,
"expiryDate": 1750173949364,
"quoteExpiryDate": 1750173949364,
"acceptanceExpiryDate": 1750172830290,
"quoteStatus": "ACCEPTED",
"reference": "7ed966de-ed04-4107-b08f-ccfc4345a9f9",
"type": "IN",
"subType": "merchantPayIn",
"status": "UNDERPAID",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 5
},
"walletCurrency": {
"currency": "USDT",
"amount": 11.572366,
"actual": 6.282366
},
"paidCurrency": {
"currency": "USDT",
"amount": 11.572366,
"actual": 6.282366
},
"feeCurrency": {
"currency": "USDT",
"amount": 0.381888,
"actual": 0.191888
},
"networkFeeCurrency": {
"currency": null,
"amount": 0,
"actual": 0
},
"displayRate": {
"base": "USDT",
"counter": "EUR",
"rate": 0.864127525866
},
"exchangeRate": {
"base": "USDT",
"counter": "USDT",
"rate": 1
},
"address": {
"address": "2Y3CHaT3JYFC5yRD9XJzNMRHjbdMfXDnzYHRWyLf8gnk",
"tag": null,
"protocol": "SOLANA",
"uri": "2Y3CHaT3JYFC5yRD9XJzNMRHjbdMfXDnzYHRWyLf8gnk",
"alternatives": [
{
"address": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"tag": null,
"protocol": "POLYGON",
"uri": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"alternatives": []
},
{
"address": "TD7LgdxRQn4X27WALU8J68HgUNAhu2XLT8",
"tag": null,
"protocol": "TRON",
"uri": "TD7LgdxRQn4X27WALU8J68HgUNAhu2XLT8",
"alternatives": []
},
{
"address": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"tag": null,
"protocol": "ETHEREUM",
"uri": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"alternatives": []
},
{
"address": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"tag": null,
"protocol": "BINANCE",
"uri": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"alternatives": []
}
]
},
"returnUrl": "https://yourwebsitename.com",
"redirectUrl": "https://pay.sandbox.layer1.com/payin/4de6afee-7446-4da2-8f00-46debbf67560",
"transactions": [
{
"dateCreated": 1750172849818,
"dateConfirmed": 1750172982102,
"hash": "0x4e804daaae2f53460ee9821e86a3acb3be740664b25006f1717e5ad82fcdf731",
"amount": 11.572366,
"networkFeeCurrency": "USDT",
"networkFeeAmount": null,
"sources": [
"0x84a4a239805d06c685219801b82bea7c76702214",
"0x84a4a239805d06c685219801b82bea7c76702214"
],
"displayRate": {
"base": "USDT",
"counter": "EUR",
"rate": 0.864127525866
},
"exchangeRate": {
"base": "USDT",
"counter": "USDT",
"rate": 1
},
"protocol": "ERC20",
"isOnHold": false
}
],
"refund": null,
"refunds": [],
"currencyOptions": null,
"flow": null,
"twoStep": false,
"metadata": {},
"customerId": null,
"pegged": false
}
}
```
status-change - EXPIRED
```json title="status-change - EXPIRED"
{
"event": "layer1:payment:checkout:status-change",
"eventId": "01977e6b-23d6-70af-94c4-0d6003fc2eec",
"timestamp": "2025-06-17T15:04:00.214983264Z",
"data": {
"uuid": "63e3fece-1e91-4d57-9bd9-35d785183486",
"merchantDisplayName": "USDT wallet",
"merchantId": "ce3fe53f-2461-46d7-9e8a-0e58003cd358",
"dateCreated": 1750171431459,
"expiryDate": 1750172640132,
"quoteExpiryDate": null,
"acceptanceExpiryDate": null,
"quoteStatus": "TEMPLATE",
"reference": "9a983440-7149-415a-9cdc-f445c9d87ad8",
"type": "OUT",
"subType": "merchantPayOut",
"status": "EXPIRED",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 0
},
"walletCurrency": {
"currency": "USDT",
"amount": 11.57236634640142803,
"actual": 0
},
"paidCurrency": {
"currency": null,
"amount": 0,
"actual": 0
},
"feeCurrency": {
"currency": "USDT",
"amount": 0,
"actual": 0
},
"networkFeeCurrency": {
"currency": null,
"amount": 0,
"actual": 0
},
"displayRate": null,
"exchangeRate": null,
"address": null,
"returnUrl": "https://yourwebsitename.com",
"redirectUrl": "https://pay.sandbox.layer1.com/payin/63e3fece-1e91-4d57-9bd9-35d785183486",
"transactions": [],
"refund": null,
"refunds": [],
"currencyOptions": null,
"flow": null,
"twoStep": false,
"metadata": {},
"customerId": null,
"pegged": false
}
}
```
transaction-held
```json title="transaction-held"
{
"event": "layer1:payment:checkout:transaction-held",
"eventId": "01977e62-0af9-7c58-81c1-aa200fc2d5a7",
"timestamp": "2025-06-17T14:54:04.025185418Z",
"data": {
"uuid": "9ec2c560-e594-47dc-9f3f-1f80847b6061",
"merchantDisplayName": "USDT wallet",
"merchantId": "ce3fe53f-2461-46d7-9e8a-0e58003cd358",
"dateCreated": 1750173279262,
"expiryDate": 1750174479262,
"quoteExpiryDate": 1750174479262,
"acceptanceExpiryDate": 1750173332123,
"quoteStatus": "ACCEPTED",
"reference": "e4d62fc0-f9b7-4c6d-90cd-dd2095db92eb",
"type": "IN",
"subType": "merchantPayIn",
"status": "PROCESSING",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 0
},
"walletCurrency": {
"currency": "USDT",
"amount": 11.572366,
"actual": 0
},
"paidCurrency": {
"currency": "USDT",
"amount": 11.572366,
"actual": 0
},
"feeCurrency": {
"currency": "USDT",
"amount": 0.381888,
"actual": 0
},
"networkFeeCurrency": {
"currency": null,
"amount": 0,
"actual": 0
},
"displayRate": {
"base": "USDT",
"counter": "EUR",
"rate": 0.864127525866
},
"exchangeRate": {
"base": "USDT",
"counter": "USDT",
"rate": 1
},
"address": {
"address": "97PSD43qjie9qrzBY1XEicc2yraWxNS7L2XwWYjEma9D",
"tag": null,
"protocol": "SOLANA",
"uri": "97PSD43qjie9qrzBY1XEicc2yraWxNS7L2XwWYjEma9D",
"alternatives": [
{
"address": "TVBruw4py4T6GVFEFCsCdjxD9e2oU3D1B8",
"tag": null,
"protocol": "TRON",
"uri": "TVBruw4py4T6GVFEFCsCdjxD9e2oU3D1B8",
"alternatives": []
},
{
"address": "0xd2cf07492ccc4b0d8b1f26b282701a8dcf064509",
"tag": null,
"protocol": "POLYGON",
"uri": "0xd2cf07492ccc4b0d8b1f26b282701a8dcf064509",
"alternatives": []
},
{
"address": "0xd2cf07492ccc4b0d8b1f26b282701a8dcf064509",
"tag": null,
"protocol": "ETHEREUM",
"uri": "0xd2cf07492ccc4b0d8b1f26b282701a8dcf064509",
"alternatives": []
},
{
"address": "0xd2cf07492ccc4b0d8b1f26b282701a8dcf064509",
"tag": null,
"protocol": "BINANCE",
"uri": "0xd2cf07492ccc4b0d8b1f26b282701a8dcf064509",
"alternatives": []
}
]
},
"returnUrl": "https://yourwebsitename.com",
"redirectUrl": "https://pay.sandbox.layer1.com/9ec2c560-e594-47dc-9f3f-1f80847b6061",
"transactions": [
{
"dateCreated": 1750173338502,
"dateConfirmed": null,
"hash": "0x4611f8178a98b42d4d47b8978a6b820999ce156f114f62e558ebdaaf4f5a2fa7",
"amount": 20,
"networkFeeCurrency": "USDT",
"networkFeeAmount": null,
"sources": [
"0x84a4a239805d06c685219801b82bea7c76702214",
"0x84a4a239805d06c685219801b82bea7c76702214"
],
"displayRate": {
"base": "USDT",
"counter": "EUR",
"rate": 0.864127525866
},
"exchangeRate": {
"base": "USDT",
"counter": "USDT",
"rate": 1
},
"protocol": "ERC20",
"isOnHold": true
}
],
"refund": null,
"refunds": [],
"currencyOptions": null,
"flow": null,
"twoStep": false,
"metadata": {},
"customerId": null,
"pegged": false
}
}
```
status-change - CANCELLED
```json title="status-change - CANCELLED"
{
"event": "layer1:payment:checkout:status-change",
"eventId": "01977e6e-56ab-70e0-849a-538e375d49b8",
"timestamp": "2025-06-17T15:07:29.835356203Z",
"data": {
"uuid": "4de6afee-7446-4da2-8f00-46debbf67560",
"merchantDisplayName": "USDT wallet",
"merchantId": "ce3fe53f-2461-46d7-9e8a-0e58003cd358",
"dateCreated": 1750172749364,
"expiryDate": 1750173949364,
"quoteExpiryDate": 1750173949364,
"acceptanceExpiryDate": 1750172830290,
"quoteStatus": "ACCEPTED",
"reference": "7ed966de-ed04-4107-b08f-ccfc4345a9f9",
"type": "IN",
"subType": "merchantPayIn",
"status": "CANCELLED",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 0
},
"walletCurrency": {
"currency": "USDT",
"amount": 11.572366,
"actual": 0
},
"paidCurrency": {
"currency": "USDT",
"amount": 11.572366,
"actual": 0
},
"feeCurrency": {
"currency": "USDT",
"amount": 0.381888,
"actual": 0
},
"networkFeeCurrency": {
"currency": null,
"amount": 0,
"actual": 0
},
"displayRate": {
"base": "USDT",
"counter": "EUR",
"rate": 0.864127525866
},
"exchangeRate": {
"base": "USDT",
"counter": "USDT",
"rate": 1
},
"address": {
"address": "2Y3CHaT3JYFC5yRD9XJzNMRHjbdMfXDnzYHRWyLf8gnk",
"tag": null,
"protocol": "SOLANA",
"uri": "2Y3CHaT3JYFC5yRD9XJzNMRHjbdMfXDnzYHRWyLf8gnk",
"alternatives": [
{
"address": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"tag": null,
"protocol": "POLYGON",
"uri": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"alternatives": []
},
{
"address": "TD7LgdxRQn4X27WALU8J68HgUNAhu2XLT8",
"tag": null,
"protocol": "TRON",
"uri": "TD7LgdxRQn4X27WALU8J68HgUNAhu2XLT8",
"alternatives": []
},
{
"address": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"tag": null,
"protocol": "ETHEREUM",
"uri": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"alternatives": []
},
{
"address": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"tag": null,
"protocol": "BINANCE",
"uri": "0x22721e34668fe4944bbfdc6267db9d1cf1aa827c",
"alternatives": []
}
]
},
"returnUrl": "https://yourwebsitename.com",
"redirectUrl": "https://pay.sandbox.layer1.com/payin/4de6afee-7446-4da2-8f00-46debbf67560",
"transactions": [],
"refund": null,
"refunds": [],
"currencyOptions": null,
"flow": null,
"twoStep": false,
"metadata": {},
"customerId": null,
"pegged": false
}
}
```
transaction-late
```json title="transaction-late"
{
"event": "layer1:payment:checkout:transaction-late",
"eventId": "01977e6b-23d6-70af-94c4-0d6003fc2eec",
"timestamp": "2025-06-17T15:04:00.214983264Z",
"data": {
"uuid": "63e3fece-1e91-4d57-9bd9-35d785183486",
"merchantDisplayName": "USDT wallet",
"merchantId": "ce3fe53f-2461-46d7-9e8a-0e58003cd358",
"dateCreated": 1750171431459,
"expiryDate": 1750172640132,
"quoteExpiryDate": null,
"acceptanceExpiryDate": null,
"quoteStatus": "TEMPLATE",
"reference": "9a983440-7149-415a-9cdc-f445c9d87ad8",
"type": "OUT",
"subType": "merchantPayOut",
"status": "EXPIRED",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 10
},
"walletCurrency": {
"currency": "USDT",
"amount": 11.57236634640142803,
"actual": 11.57236634640142803
},
"paidCurrency": {
"currency": "USDT",
"amount": 11.57236634640142803,
"actual": 11.57236634640142803
},
"feeCurrency": {
"currency": "USDT",
"amount": 0.39764745,
"actual": 0.39764745
},
"networkFeeCurrency": {
"currency": null,
"amount": 0,
"actual": 0
},
"displayRate": null,
"exchangeRate": null,
"address": null,
"returnUrl": "https://yourwebsitename.com",
"redirectUrl": "https://pay.sandbox.layer1.com/payin/63e3fece-1e91-4d57-9bd9-35d785183486",
"transactions": [],
"refund": null,
"refunds": [],
"currencyOptions": null,
"flow": null,
"twoStep": false,
"metadata": {},
"customerId": null,
"pegged": false
}
}
```
---
## Listening for payout webhooks
Layer1 sends out a series of webhooks to keep you informed on any lifecycle event for the payment you have created.
The body of a webhook can be divided into two main different sections, `event` and `data`. The event will be what event has occurred within the product source of the webhook, and data will be all of the relevant payment information sent with the webhook.
### Event types
| Event | Description |
| :----------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------ |
| `layer1:payment:checkout:status-change` | Happens twice, the status of the payment changes from `pending` to `processing`, `PENDING` to `EXPIRED` or from `processing` to `complete`. |
| `layer1:payment:checkout:transaction-held` | A payment has been placed on hold for screening. |
### Data types
Data in each webhook is comprehensive, but we will highlight some of the most important fields here:
| Field | Description |
| :---------------- | :----------------------------------------------------------------------------------------------------------------------------- |
| `uuid` | UUID of the linked payment, will remain the same across all webhook types for that payment. |
| `type` | The type of payment, can be IN for deposit or OUT for payout |
| `status` | The current status of the payment. |
| `displayCurrency` | The amounts in the selected display currency that the end user will see. |
| `paidCurrency` | The amounts in the digital asset sent by the merchant to end-user. |
| `walletCurrency` | The amounts debited from wallet used to fund the payment. |
| `amount` | The amount requested when creating a payment. |
| `actual` | The actual amount of money sent once the payment is confirmed on the blockchain. |
| `transactions` | The array of transactions linked to the payment, if multiple payments are sent to the same address, they will all appear here. |
## Payment flow diagram and webhooks sent
## Webhook sequence for each scenario
### Payment complete webhooks
Upon the end user successfully completing the payment within the allocated time frame and with the correct amount, you'll receive the following webhooks:
1. **statusChanged - Processing:** Dispatched when the payment status updates to processing after the transaction is recognized on the platform.
2. **statusChanged - Complete:** Issued when the payment status updates to complete following blockchain confirmation.
The statusChanged - Complete webhook delivers comprehensive payment details, including the final amounts sent (`actual` fields) and the transaction hash within the `transactions` array.
### Payout webhook examples
status-change - PROCESSING
```json title="status-change - PROCESSING"
{
"event": "layer1:payment:checkout:status-change",
"eventId": "01977e61-0ef9-77a2-90bb-9933922f72ae",
"timestamp": "2025-06-17T14:52:59.513989735Z",
"data": {
"uuid": "f92f0ac0-8f91-43d2-b73c-75061c222d47",
"merchantDisplayName": "USDT wallet",
"merchantId": "ce3fe53f-2461-46d7-9e8a-0e58003cd358",
"dateCreated": 1750171979150,
"expiryDate": 1750173179150,
"quoteExpiryDate": 1750173179150,
"acceptanceExpiryDate": 1750172009214,
"quoteStatus": "ACCEPTED",
"reference": "437f100c-e37e-401e-9b32-aec349843d05",
"type": "OUT",
"subType": "merchantPayOut",
"status": "PROCESSING",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 0
},
"walletCurrency": {
"currency": "USDT",
"amount": 11.57236634640142803,
"actual": 11.57236634640142803
},
"paidCurrency": {
"currency": "USDT",
"amount": 11.57236634640142803,
"actual": 0
},
"feeCurrency": {
"currency": "USDT",
"amount": 0.381888089039204284,
"actual": 0
},
"networkFeeCurrency": {
"currency": "USDT",
"amount": 1.15723663,
"actual": 0
},
"displayRate": {
"base": "USDT",
"counter": "EUR",
"rate": 0.864127525866
},
"exchangeRate": {
"base": "USDT",
"counter": "USDT",
"rate": 1
},
"address": {
"address": "0x84A4a239805d06c685219801B82BEA7c76702214",
"tag": "",
"protocol": "ERC20",
"uri": "ethereum:0xdac17f958d2ee523a2206206994597c13d831ec7/transfer?address=0x84A4a239805d06c685219801B82BEA7c76702214&uint256=11572366",
"alternatives": []
},
"returnUrl": "https://yourwebsitename.com",
"redirectUrl": "https://pay.sandbox.layer1.com/payout/f92f0ac0-8f91-43d2-b73c-75061c222d47",
"transactions": [],
"refund": null,
"refunds": [],
"currencyOptions": null,
"flow": null,
"twoStep": false,
"metadata": {},
"customerId": null,
"pegged": false
}
}
```
status-change - COMPLETE
```json title="status-change - COMPLETE"
{
"event": "layer1:payment:checkout:status-change",
"eventId": "01977e62-0af9-7c58-81c1-aa200fc2d5a7",
"timestamp": "2025-06-17T14:54:04.025185418Z",
"data": {
"uuid": "f92f0ac0-8f91-43d2-b73c-75061c222d47",
"merchantDisplayName": "USDT wallet",
"merchantId": "ce3fe53f-2461-46d7-9e8a-0e58003cd358",
"dateCreated": 1750171979150,
"expiryDate": 1750173179150,
"quoteExpiryDate": 1750173179150,
"acceptanceExpiryDate": 1750172009215,
"quoteStatus": "ACCEPTED",
"reference": "437f100c-e37e-401e-9b32-aec349843d05",
"type": "OUT",
"subType": "merchantPayOut",
"status": "COMPLETE",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 10
},
"walletCurrency": {
"currency": "USDT",
"amount": 11.572366,
"actual": 11.572366
},
"paidCurrency": {
"currency": "USDT",
"amount": 11.572366,
"actual": 11.572366
},
"feeCurrency": {
"currency": "USDT",
"amount": 0.381888,
"actual": 0.38188799
},
"networkFeeCurrency": {
"currency": "USDT",
"amount": 1.157237,
"actual": 1.157237
},
"displayRate": {
"base": "USDT",
"counter": "EUR",
"rate": 0.864127525866
},
"exchangeRate": {
"base": "USDT",
"counter": "USDT",
"rate": 1
},
"address": {
"address": "0x84A4a239805d06c685219801B82BEA7c76702214",
"tag": "",
"protocol": "ERC20",
"uri": "ethereum:0xdac17f958d2ee523a2206206994597c13d831ec7/transfer?address=0x84A4a239805d06c685219801B82BEA7c76702214&uint256=11572366",
"alternatives": []
},
"returnUrl": "https://yourwebsitename.com",
"redirectUrl": "https://pay.sandbox.layer1.com/payout/f92f0ac0-8f91-43d2-b73c-75061c222d47",
"transactions": [
{
"dateCreated": 1750172044013,
"dateConfirmed": 1750172043977,
"hash": "0xff970e401af21112a245085c08be1129dc1421750f379f58df2fc2c9f87f3e27",
"amount": 11.572366,
"networkFeeCurrency": "USDT",
"networkFeeAmount": null,
"sources": [
"0xfb64535b1466a9aaf39f84fce15b971b341ca9fc",
"0xfb64535b1466a9aaf39f84fce15b971b341ca9fc"
],
"displayRate": {
"base": "USDT",
"counter": "EUR",
"rate": 0.864127525866
},
"exchangeRate": {
"base": "USDT",
"counter": "USDT",
"rate": 1
},
"protocol": "ERC20",
"isOnHold": false
}
],
"refund": null,
"refunds": [],
"currencyOptions": null,
"flow": null,
"twoStep": false,
"metadata": {},
"customerId": null,
"pegged": false
}
}
```
transaction-held
```json title="transaction-held"
{
"event": "layer1:payment:checkout:transaction-held",
"eventId": "01977e62-0af9-7c58-81c1-aa200fc2d5a7",
"timestamp": "2025-06-17T14:54:04.025185418Z",
"data": {
"uuid": "f92f0ac0-8f91-43d2-b73c-75061c222d47",
"merchantDisplayName": "USDT wallet",
"merchantId": "ce3fe53f-2461-46d7-9e8a-0e58003cd358",
"dateCreated": 1750171979150,
"expiryDate": 1750173179150,
"quoteExpiryDate": 1750173179150,
"acceptanceExpiryDate": 1750172009215,
"quoteStatus": "ACCEPTED",
"reference": "437f100c-e37e-401e-9b32-aec349843d05",
"type": "OUT",
"subType": "merchantPayOut",
"status": "PROCESSING",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 10
},
"walletCurrency": {
"currency": "USDT",
"amount": 11.572366,
"actual": 11.572366
},
"paidCurrency": {
"currency": "USDT",
"amount": 11.572366,
"actual": 11.572366
},
"feeCurrency": {
"currency": "USDT",
"amount": 0.381888,
"actual": 0.38188799
},
"networkFeeCurrency": {
"currency": "USDT",
"amount": 1.157237,
"actual": 1.157237
},
"displayRate": {
"base": "USDT",
"counter": "EUR",
"rate": 0.864127525866
},
"exchangeRate": {
"base": "USDT",
"counter": "USDT",
"rate": 1
},
"address": {
"address": "0x84A4a239805d06c685219801B82BEA7c76702214",
"tag": "",
"protocol": "ERC20",
"uri": "ethereum:0xdac17f958d2ee523a2206206994597c13d831ec7/transfer?address=0x84A4a239805d06c685219801B82BEA7c76702214&uint256=11572366",
"alternatives": []
},
"returnUrl": "https://yourwebsitename.com",
"redirectUrl": "https://pay.sandbox.layer1.com/payout/f92f0ac0-8f91-43d2-b73c-75061c222d47",
"transactions": [
{
"dateCreated": 1750172044013,
"dateConfirmed": 1750172043977,
"hash": "0xff970e401af21112a245085c08be1129dc1421750f379f58df2fc2c9f87f3e27",
"amount": 11.572366,
"networkFeeCurrency": "USDT",
"networkFeeAmount": null,
"sources": [
"0xfb64535b1466a9aaf39f84fce15b971b341ca9fc",
"0xfb64535b1466a9aaf39f84fce15b971b341ca9fc"
],
"displayRate": {
"base": "USDT",
"counter": "EUR",
"rate": 0.864127525866
},
"exchangeRate": {
"base": "USDT",
"counter": "USDT",
"rate": 1
},
"protocol": "ERC20",
"isOnHold": true
}
],
"refund": null,
"refunds": [],
"currencyOptions": null,
"flow": null,
"twoStep": false,
"metadata": {},
"customerId": null,
"pegged": false
}
}
```
status-change - EXPIRED
```json title="status-change - EXPIRED"
{
"event": "layer1:payment:checkout:status-change",
"eventId": "01977e6b-23d6-70af-94c4-0d6003fc2eec",
"timestamp": "2025-06-17T15:04:00.214983264Z",
"data": {
"uuid": "63e3fece-1e91-4d57-9bd9-35d785183486",
"merchantDisplayName": "USDT wallet",
"merchantId": "ce3fe53f-2461-46d7-9e8a-0e58003cd358",
"dateCreated": 1750171431459,
"expiryDate": 1750172640132,
"quoteExpiryDate": null,
"acceptanceExpiryDate": null,
"quoteStatus": "TEMPLATE",
"reference": "9a983440-7149-415a-9cdc-f445c9d87ad8",
"type": "OUT",
"subType": "merchantPayOut",
"status": "EXPIRED",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 0
},
"walletCurrency": {
"currency": "USDT",
"amount": 11.57236634640142803,
"actual": 0
},
"paidCurrency": {
"currency": null,
"amount": 0,
"actual": 0
},
"feeCurrency": {
"currency": "USDT",
"amount": 0,
"actual": 0
},
"networkFeeCurrency": {
"currency": null,
"amount": 0,
"actual": 0
},
"displayRate": null,
"exchangeRate": null,
"address": null,
"returnUrl": "https://yourwebsitename.com",
"redirectUrl": "https://pay.sandbox.layer1.com/payout/63e3fece-1e91-4d57-9bd9-35d785183486",
"transactions": [],
"refund": null,
"refunds": [],
"currencyOptions": null,
"flow": null,
"twoStep": false,
"metadata": {},
"customerId": null,
"pegged": false
}
}
```
---
## (Optional) Pre-selecting the digital asset to pay
To streamline the process of redirecting end-users directly to the payment address page for digital asset transactions, utilize the [Create Payment](/l1-api/crypto-payments/payment-create) endpoint, incorporating `payInDetails`.
Below are the details required for the create payment request that bypasses the cryptocurrency selection stage. Set `type` to `IN`, and use the Merchant ID from the Merchant Details page as `merchantId`. Set `payInDetails.currency` to the digital asset the end-user pays with to skip the selection screen; if that asset is available on multiple protocols, also set `payInDetails.protocol`. Leave `payInDetails` out to let the end-user choose.
## Example Request
Here is how an example request might look:
```json title="Example Request"
{
"merchantId": "00a4af9d-ad4b-42d5-bec4-b7a8c90161fe",
"amount": 100,
"expiryMinutes": 60,
"currency": "USD",
"returnUrl": "https://yourwebsitename.com",
"reference": "6a4013c2-5fa9-4e2f-bef7-88c5570b6501",
"type": "IN",
"payInDetails": {
"currency": "USDT",
"protocol": "TRC20"
}
}
```
## Example Response
Upon successfully submitting the request, you'll receive a response like the following:
Response
```json title="Example Response"
{
"uuid": "fef4e99d-b41d-4efe-89fd-4fdf41f3f33f",
"merchantDisplayName": "Metallica Inc",
"merchantId": "00a4af9d-ad4b-42d5-bec4-b7a8c90161fe",
"dateCreated": 1704807605678,
"expiryDate": 1704808805678,
"quoteExpiryDate": 1704808806000,
"acceptanceExpiryDate": 1704807636000,
"quoteStatus": "ACCEPTED",
"reference": "a003cec4-abf5-42aa-baf0-43a1cc815536",
"type": "IN",
"subType": "merchantPayIn",
"status": "PENDING",
"displayCurrency": {
"currency": "USD",
"amount": 100,
"actual": 0
},
"walletCurrency": {
"currency": "EUR",
"amount": 92.74,
"actual": 0
},
"paidCurrency": {
"currency": "USDT",
"amount": 99.935377,
"actual": 0
},
"feeCurrency": {
"currency": "EUR",
"amount": 0.93,
"actual": 0
},
"displayRate": {
"base": "USDT",
"counter": "USD",
"rate": 0.998867803322
},
"exchangeRate": {
"base": "USDT",
"counter": "EUR",
"rate": 0.92635
},
"address": {
"address": "TTwyxkKzenGBhVX2TsjYLVFfApEQ3hPXGc",
"tag": null,
"protocol": "TRC20",
"uri": "TTwyxkKzenGBhVX2TsjYLVFfApEQ3hPXGc",
"alternatives": []
},
"returnUrl": "https://yourwebsitename.com",
"redirectUrl": "https://pay.sandbox.layer1.com/payin?uuid=fef4e99d-b41d-4efe-89fd-4fdf41f3f33f",
"transactions": [],
"refund": null,
"refunds": []
}
```
Key takeaways include the `uuid` for payment queries and the `redirectURL` for directing the end-user to the Hosted Payments Page, bypassing the first digital asset selection screen if `payInDetails.currency` is provided.
This setup ensures a tailored payment experience, directly linking to the specified payment address. All Exchange rates are now provided given that the payment currency is known at the time the payment is created.
---
## (Optional) Pre-selecting the digital asset to receive
To streamline the process of redirecting end users directly to the payment address page for digital asset transactions, utilize the [Create Payment](/l1-api/crypto-payments/payment-create) endpoint, incorporating `payOutDetails`.
Below are the details required for the create payment request that bypasses the digital asset selection stage. Set `type` to `OUT`, and use the Merchant ID from the Merchant Details page as `merchantId`. Set `payOutDetails.currency` to the digital asset the end-user receives to skip the selection screen; if that asset supports multiple protocols, also set `payOutDetails.protocol`. Leave `payOutDetails` out to let the end-user choose.
## Example Request
Here is how an example request might look:
```json title="Example Request"
{
"currency": "EUR",
"amount": 10,
"reference": "a0c95e46-28f6-403d-a612-735ff21bd38f",
"merchantId": "ce3fe53f-2461-46d7-9e8a-0e58003cd358",
"expiryMinutes": 20,
"returnUrl": "https://yourwebsitename.com",
"type": "OUT",
"payOutDetails": {
"code": "crypto",
"currency": "USDT",
"protocol": "ERC20",
"address": "0x84A4a239805d06c685219801B82BEA7c76702215"
}
}
```
## Example Response
Upon successfully submitting the request, you'll receive a response like the following:
Response
```json title="Example Response"
{
"metadata": {},
"uuid": "11db7d1b-97cf-4e71-ac73-ef9e8e505331",
"merchantDisplayName": "USDT wallet",
"merchantId": "ce3fe53f-2461-46d7-9e8a-0e58003cd358",
"dateCreated": 1750171656561,
"expiryDate": 1750172856561,
"quoteExpiryDate": 1750172856561,
"acceptanceExpiryDate": 1750171686592,
"quoteStatus": "ACCEPTED",
"reference": "a0c95e46-28f6-403d-a612-735ff21bd38f",
"type": "OUT",
"subType": "merchantPayOut",
"status": "PROCESSING",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 0
},
"walletCurrency": {
"currency": "USDT",
"amount": 11.57236634640142803,
"actual": 11.57236634640142803
},
"paidCurrency": {
"currency": "USDT",
"amount": 11.57236634640142803,
"actual": 0
},
"feeCurrency": {
"currency": "USDT",
"amount": 0.381888089039204284,
"actual": 0
},
"displayRate": {
"base": "USDT",
"counter": "EUR",
"rate": 0.864127525866
},
"exchangeRate": {
"base": "USDT",
"counter": "USDT",
"rate": 1
},
"address": {
"address": "0x84A4a239805d06c685219801B82BEA7c76702214",
"tag": "",
"protocol": "ERC20",
"uri": "ethereum:0xdac17f958d2ee523a2206206994597c13d831ec7/transfer?address=0x84A4a239805d06c685219801B82BEA7c76702214&uint256=11572366",
"alternatives": []
},
"returnUrl": "https://yourwebsitename.com",
"redirectUrl": "https://pay.sandbox.layer1.com/payout/11db7d1b-97cf-4e71-ac73-ef9e8e505331",
"transactions": [],
"refund": null,
"refunds": []
}
```
Key takeaways include the `uuid` for payment queries and the `redirectURL` for directing the end-user to the Hosted Payments Page, bypassing the first digital asset selection screen if `payOutDetails.currency` is provided.
This setup ensures a tailored payment experience, directly linking to the specified payment address. All Exchange rates are now provided given that the payment currency is known at the time the payment is created.
Next, see [Listening for payout webhooks](/layer1/use-cases/merchant-payments/hosted-checkout-digital-asset-payments/listening-for-payout-webhooks).
---
## Handle payment in exceptions
The final step is to build in handling for exceptions that may occur in the payment flow. We have discussed the types of webhooks in a previous page for these scenarios, but the following pages explain the scenarios in greater detail.
As cryptocurrency payments are push payments rather than pull payments, we cannot control when and how much cryptocurrency will be received to complete the end user's payment.
Generally, exception scenarios are caused either by end user error, hidden or unpredictable fee amounts from the sending exchange, or slow blockchain processing speeds.
There are three payment exception scenarios that need to be considered and handled by merchants, and all three can be handles from the webhook data sent:
- Overpayments: the amount received exceeds the invoice.
- Underpayments: the amount received falls short of invoice.
- Late payments: funds arrive after the payment has expired or been finalised.
## Overpayments
When a customer sends more crypto than the invoice specifies (for example, 0.1 ETH instead of 0.01 ETH), the payment still moves to a `COMPLETE` status, but the excess must be accounted for.
To see the actual received amount, on the [transactionConfirmed](/layer1/use-cases/merchant-payments/hosted-checkout-digital-asset-payments/listening-for-payment-in-webhooks) webhook, compare the fields:
- `paidCurrency.actual` indicates the amount you actually received.
- `paidCurrency.amount` indicates the original expected amount.
To ensure the best customer experience when managing overpayments, it is advisable to adjust the final payment made by the end user to reflect the higher total amount received.
This method eliminates the need to return minor excess amounts of cryptocurrency. Instead, the complete sum will be credited to the end user's account. The total amount sent will be deposited into the MID wallet associated with the transaction.
The sent amount is indicated in the `.actual` fields, which can then be referenced to update the end user's total payment amount in your system, in relation to the requested `.amount` field.
## Underpayments
Underpayments occur when end users do not transfer enough cryptocurrency to fulfil their payment obligation. This can occur if the end user is charged a network fee by their exchange platform that exceeds what they anticipated from their payment amount. Furthermore, the end user might unintentionally send less than intended (e.g. they mean to send 0.1 ETH but send 0.01 ETH instead). In this situation, the payment will still transition to an `UNDERPAID` status.
To see the actual received amount, on the [transactionConfirmed](/layer1/use-cases/merchant-payments/hosted-checkout-digital-asset-payments/listening-for-payment-in-webhooks) webhook, compare the fields:
- `paidCurrency.actual` indicates the amount you actually received.
- `paidCurrency.amount` indicates the original expected amount.
Adjust the end user's final payment to reflect the lower total amount received. This approach prevents the need to return the entire sum of crypto, which may be slightly less than expected due to fees or rounding errors, and instead, credits the revised amount to the end user's account.
The full amount sent will be deposited into the MID wallet associated with the transaction.
The sent amount is indicated in the `.actual` fields referenced in the preceding section, which can then be utilised to modify the end user's total payment amount in your system, aligning with the `.amount` field.
## Late Payments
Late payments occur when cryptocurrency arrives after a payment has already expired or been marked as `COMPLETE` or `UNDERPAID`. This situation can arise if:
- The blockchain confirmation is delayed
- The user sends funds late, or
- They attempt to top up an already finalised payment
When this happens, you’ll receive a [transactionLate](/layer1/use-cases/merchant-payments/hosted-checkout-digital-asset-payments/listening-for-payment-in-webhooks) webhook indicating that funds have been received for a payment with status of `EXPIRED`, `COMPLETE`, or `UNDERPAID`. The `walletCurrency.actual` field will show the amount credited to your wallet in the settlement currency.
### Why and when payments expire
We don’t want to leave payments in a `PENDING` state indefinitely if a user never sends any funds. Therefore, each payment has an expiry window:
- If you specify `expiryMinutes` and the payment is still in a `pending` or `processing` state after that time, it will expire once that many minutes elapse without reaching a `COMPLETE` or `UNDERPAID` status.
- If you don’t specify `expiryMinutes`, your merchant account’s default expiry setting applies.
- If a payment requires FX conversion between the currency being paid and the ond of your MID wallet but `expiryMinutes` and a default expiry are not configured, it will expire after **180 minutes** by default.
Once a payment expires, you receive a [`statusChanged`](/layer1/use-cases/merchant-payments/hosted-checkout-digital-asset-payments/listening-for-payment-in-webhooks) webhook showing `"status": "EXPIRED"`.
:::note Heads up
In most cases, the payment’s status remains `EXPIRED` after funds have been received. If an additional payment is received for a `COMPLETE` or `UNDERPAID` payment, this is still categorised as 'late', and the payments will remain in those states as they are considered final.
:::
The webhook payload includes:
- `walletCurrency.actual`: the converted amount credited to your wallet at the new spot rate (because the original locked rate no longer applies).
- Original exchange rate and total amount sent: in case you need to reconcile or report.
From this information, you can see exactly how much was received, how it was converted, and update your records accordingly.
---
## Payout
The Payout flow allows merchant's end-users to select their digital asset of choice to receive.
## Payout Sequence Diagram

The general happy path will be discussed, with exception scenarios discussed later on in the guide:
1. The end user will request to make a payout on your system, which will then be forward onto us in the form of a Create Payment request.
2. The Create Payment endpoint will return a redirectUrl, along with other useful data in the API response. End user can then be directed to this URL which hosts our payments page
3. The end user will select the digital asset and network in the dropdown that they want to receive, and as this is now on our hosted page, the request is sent directly to us.
4. The end-user inputs the receiving blockchain address to match the selected digital asset and network, the address is auto-validate to ensure its the correct format.
5. The end user initiates the payout, which will then cause multiple related webhooks to be sent to your webhook listener as the payment goes through its lifecycle.
6. Once you receive the final `COMPLETE`webhook you can poll the Get Payment endpoint to confirm the status and final paid amount, or take the information from the webhook.
7. Update the end users account accordingly with the confirmed paid amount.
## Payment Lifecycle
A payment undergoes several status changes throughout its lifecycle, detailed as follows:
| Status | Immutable? | Description |
| :--------- | :--------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Pending | No | This state indicated that the payout has been created, but that no wallet details have yet been entered. |
| Processing | No | This status indicates that the customer's payment has been submitted to the blockchain. The payment remains in this state until funds are credited to their wallet. |
| Complete | Yes | This status means that the funds have been received and credited to the users account in the chosen currency. |
## Initiating the payout
To initiate a payment process, utilize the [Create Payment](/l1-api/crypto-payments/payment-create) endpoint.
Below are the details required for the create payment request. Set `type` to `OUT`, and use the Merchant ID from the Merchant Details page as `merchantId`. If you omit `expiryMinutes`, the payment expires after 1440 minutes.
## Example Request
Here is how an example request might look:
```json title="Example Request"
{
"merchantId": "00a4af9d-ad4b-42d5-bec4-b7a8c90161fe",
"amount": 100,
"expiryMinutes": 60,
"currency": "USD",
"returnUrl": "https://yourwebsitename.com",
"reference": "6a4013c2-5fa9-4e2f-bef7-88c5570b6501",
"type": "OUT"
}
```
## Example Response
Upon successfully submitting the request, you'll receive a response like the following:
Response
```json title="Example Response"
{
"metadata": {},
"uuid": "63e3fece-1e91-4d57-9bd9-35d785183486",
"merchantDisplayName": "USDT wallet",
"merchantId": "ce3fe53f-2461-46d7-9e8a-0e58003cd358",
"dateCreated": 1750171431459,
"expiryDate": 1750172631459,
"quoteExpiryDate": null,
"acceptanceExpiryDate": null,
"quoteStatus": "TEMPLATE",
"reference": "b91f3d03-7a6e-4502-b865-784cb5fa21c4",
"type": "OUT",
"subType": "merchantPayOut",
"status": "PENDING",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 0
},
"walletCurrency": {
"currency": "USDT",
"amount": 11.57236634640142803,
"actual": 0
},
"paidCurrency": {
"currency": null,
"amount": 0,
"actual": 0
},
"feeCurrency": {
"currency": "USDT",
"amount": 0,
"actual": 0
},
"displayRate": null,
"exchangeRate": null,
"address": null,
"returnUrl": "https://yourwebsitename.com",
"redirectUrl": "https://pay.sandbox.layer1.com/payout/fef4e99d-b41d-4efe-89fd-4fdf41f3f33f",
"transactions": [],
"refund": null,
"refunds": []
}
```
At this juncture, it's crucial to note the `uuid` and `redirectUrl`.
The uuid is used for querying the payment status, and the redirectUrl directs the end-user to the Hosted Payments Page for digital asset selection.
Note that as the payment currency is not known at this stage, exchange rates and paid currency values are `null`
:::note
If you already know the digital asset and address of the end-user will pay with, refer to the next guide for creating a tailored payment request.
:::
---
## Hosted checkout for digital asset payments
:::note
This feature is for your merchant to use directly.
:::
This guide outlines the process for incorporating digital asset payments into your workflow with Layer1, leveraging the Hosted Payments Page (HPP) as a user interface component for end users to complete their payments both IN and OUT.
Additionally, all information presented on the page is accessible via API response, allowing for the customization of your own hosted payments page as desired.
A sample of our Hosted Payment Page experience.
## Steps to Follow
This guide covers the integration of payment links into your platform to accept crypto payments, detailing the following steps:
1. Initiating a payment request and handling the response.
2. Guiding the end user to complete the payment.
3. Setting up a webhook listener for payment notifications.
4. Managing payment exceptions and errors.
Start by initiating the payment request: [Payment in](/layer1/use-cases/merchant-payments/hosted-checkout-digital-asset-payments/creating-a-payment-in).
---
## Create a payout
To initiate a payment out, send the [`POST /api/v1/pay/summary`](/l1-api/crypto-payments/payment-create) request with `type` set to `OUT`. Remember to add the mandatory `payOutDetails` object. When using `payOutDetails`, the address is validated to ensure that the payment is not created with an invalid address.
```json title="Example Request"
{
"merchantId": "00a4af9d-ad4b-42d5-bec4-b7a8c90161fe",
"amount": 100,
"expiryMinutes": 60,
"currency": "USD",
"returnUrl": "https://yourwebsitename.com",
"reference": "6a4013c2-5fa9-4e2f-bef7-88c5570b6501",
"type": "OUT",
"payOutDetails": {
"code": "crypto",
"currency": "USDT",
"protocol": "ERC20",
"address": "0x84A4a239805d06c685219801B82BEA7c76702214",
"tag": ""
}
}
```
Find your `merchantId` in the Portal, in **Merchant Payments** > **Merchant accounts** > **Merchant Details**; the funds are drawn from that wallet. In `payOutDetails`, set `code` to `crypto`, and set `protocol` when the currency is available on multiple protocols (for example, USDT on `ERC20` or `TRC20`). For XRP, `tag` is required; use `0` if the receiving wallet has no tag. If you omit `expiryMinutes`, the end-user has 1440 minutes to claim the payout.
Upon successful submission, the following response will be received:
Response
```json title="Example Response"
{
"uuid": "fef4e99d-b41d-4efe-89fd-4fdf41f3f33f",
"merchantDisplayName": "Metallica Inc",
"merchantId": "00a4af9d-ad4b-42d5-bec4-b7a8c90161fe",
"dateCreated": 1704807605678,
"expiryDate": 1704808805678,
"quoteExpiryDate": 1704808806000,
"acceptanceExpiryDate": 1704807636000,
"quoteStatus": "ACCEPTED",
"reference": "a003cec4-abf5-42aa-baf0-43a1cc815536",
"type": "OUT",
"subType": "merchantPayOut",
"status": "PROCESSING",
"displayCurrency": {
"currency": "USD",
"amount": 50,
"actual": 0
},
"walletCurrency": {
"currency": "ETH",
"amount": 0.0176227,
"actual": 0.0176227
},
"paidCurrency": {
"currency": "USDT",
"amount": 50,
"actual": 0
},
"feeCurrency": {
"currency": "ETH",
"amount": 0.00017623,
"actual": 0
},
"displayRate": {
"base": "ETH",
"counter": "USD",
"rate": 2837.24968364666
},
"exchangeRate": {
"base": "ETH",
"counter": "ETH",
"rate": 1
},
"address": {
"address": "0x84A4a239805d06c685219801B82BEA7c76702214",
"tag": "",
"protocol": "ETH",
"uri": "ethereum:0x84A4a239805d06c685219801B82BEA7c76702214?value=1.76227E+16",
"alternatives": []
},
"returnUrl": "www.your-url-here.com",
"redirectUrl": "https://pay.sandbox.bvnk.com/payout?uuid=46a86ebc-c40b-464f-b017-00191be0f20c",
"transactions": [],
"refund": null,
"refunds": []
}
```
The response indicates that the payment has been automatically approved and is in a `PROCESSING` state.
Key takeaways include the following:
- Provided `uuid` can be used at any point to query the status of the payment. A series of webhook notifications will be dispatched to allow you to update your system accordingly.
---
## Receive payout notifications
Layer1 sends out a series of webhooks to keep you informed on any lifecycle event for the payment you have created.
## Webhook structure
The body of a webhook can be divided into three different sections:
- `source`. Specifies the product source of the webhook.
- `event`: Specifies an event that occurred within that source.
- `data`. Indicates relevant payment information sent with the webhook.
### Source types
For a payout, the source will always be `payment`.
### Event types
| Event | Description |
| :-------------------------------------- | :------------------------------------------------------------------------------------------------------------------- |
| `layer1:payment:checkout:status-change` | Happens twice, the status of the payment changes from `pending` to `processing`, or from `processing` to `complete`. |
### Data types
Data in each webhook is comprehensive. Note the most important fields here:
| Field | Description |
| :---------------- | :------------------------------------------------------------------------------------- |
| `uuid` | UUID of the linked payment. Remains the same across all webhook types for that payment |
| `type` | Type of payment, can be IN for deposit or OUT for withdrawal |
| `status` | Current status of the payment. The `COMPLETE` status is final |
| `displayCurrency` | Amounts in the selected display currency that the end user sees |
| `paidCurrency` | Amounts in the currency actually sent by the end user when fulfilling the payment |
| `walletCurrency` | Amounts in the settlement or wallet currency linked to the payment |
| `amount` | Amount requested when creating a payment |
| `actual` | Actual amount of money sent once the payment is confirmed on the blockchain |
## Payout flow

## Sequence for the Payment Complete webhook
When the end user claims the payout, the following webhooks are sent out:
| Webhook Event Name | Description |
| --- | --- |
| **statusChanged - Processing:** | Dispatched when the payment status updates to `processing` after the transaction is recognized on the platform. This occurs when the payout is automatically processed using `payOutDetails` |
| **statusChanged - Complete:** | Dispatched when the payment status updates to `complete`, indicating that the transaction has been successfully confirmed on the blockchain.This webhook provides detailed information about the payment, including the transaction hash, which can be verified using a block explorer within the `transactions` array |
:::tip
For payout payments, exception scenarios, such as payment failures or errors, are not applicable and thus do not require consideration.
:::
The following is a comprehensive collection of the data sent for all of the webhooks for payouts:
statusChanged - PROCESSING
```json title="statusChanged - PROCESSING"
{
"source": "payment",
"event": "statusChanged",
"data": {
"uuid": "07905528-d72e-40dd-a1b4-fb8ec2f748c8",
"merchantDisplayName": "ETH Merchant ",
"merchantId": "a7eddadd-fd9b-45fb-82a1-dc12eaa14cba",
"dateCreated": 1709896869000,
"expiryDate": 1709900469000,
"quoteExpiryDate": 1709900469000,
"acceptanceExpiryDate": 1709896938000,
"quoteStatus": "ACCEPTED",
"reference": "test_reference_out_mH9LBR1",
"type": "OUT",
"subType": "merchantPayOut",
"status": "PROCESSING",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 0
},
"walletCurrency": {
"currency": "ETH",
"amount": 0.00276456,
"actual": 0.00276456
},
"paidCurrency": {
"currency": "ETH",
"amount": 0.00276456,
"actual": 0
},
"feeCurrency": {
"currency": "ETH",
"amount": 0.00002765,
"actual": 0
},
"displayRate": {
"base": "ETH",
"counter": "EUR",
"rate": 3617.212142257719
},
"exchangeRate": {
"base": "ETH",
"counter": "ETH",
"rate": 1
},
"address": {
"address": "0x84A4a239805d06c685219801B82BEA7c76702214",
"tag": null,
"protocol": null,
"uri": "ethereum:0x84A4a239805d06c685219801B82BEA7c76702214?value=2.76456E+15",
"alternatives": []
},
"returnUrl": "www.your-url-here.com",
"redirectUrl": "https://pay.sandbox.bvnk.com/payout?uuid=07905528-d72e-40dd-a1b4-fb8ec2f748c8",
"transactions": [],
"refund": null,
"refunds": []
}
}
```
statusChanged - COMPLETE
```json title="statusChanged - COMPLETE"
{
"source": "payment",
"event": "statusChanged",
"data": {
"uuid": "07905528-d72e-40dd-a1b4-fb8ec2f748c8",
"merchantDisplayName": "ETH Merchant ",
"merchantId": "a7eddadd-fd9b-45fb-82a1-dc12eaa14cba",
"dateCreated": 1709896869000,
"expiryDate": 1709900469000,
"quoteExpiryDate": 1709900469000,
"acceptanceExpiryDate": 1709896938000,
"quoteStatus": "ACCEPTED",
"reference": "test_reference_out_mH9LBR1",
"type": "OUT",
"subType": "merchantPayOut",
"status": "COMPLETE",
"displayCurrency": {
"currency": "EUR",
"amount": 10,
"actual": 10
},
"walletCurrency": {
"currency": "ETH",
"amount": 0.00276456,
"actual": 0.00276456
},
"paidCurrency": {
"currency": "ETH",
"amount": 0.00276456,
"actual": 0.00276456
},
"feeCurrency": {
"currency": "ETH",
"amount": 0.00002765,
"actual": 0.00002765
},
"displayRate": {
"base": "ETH",
"counter": "EUR",
"rate": 3617.212142257719
},
"exchangeRate": {
"base": "ETH",
"counter": "ETH",
"rate": 1
},
"address": {
"address": "0x84A4a239805d06c685219801B82BEA7c76702214",
"tag": null,
"protocol": null,
"uri": "ethereum:0x84A4a239805d06c685219801B82BEA7c76702214?value=2.76456E+15",
"alternatives": []
},
"returnUrl": "www.your-url-here.com",
"redirectUrl": "https://pay.sandbox.bvnk.com/payout?uuid=07905528-d72e-40dd-a1b4-fb8ec2f748c8",
"transactions": [
{
"dateCreated": 1709897097718,
"dateConfirmed": 1709897097718,
"hash": "0x30c18d5eed6c02418506d69d87c59ee52c2e10753bbccea035346b81c1e1a7e0",
"amount": 0.00276456,
"risk": {
"level": "LOW",
"resourceName": null,
"resourceCategory": null,
"alerts": []
},
"networkFeeCurrency": "ETH",
"networkFeeAmount": 0,
"sources": [],
"displayRate": {
"base": "ETH",
"counter": "EUR",
"rate": 3617.212142257719
},
"exchangeRate": {
"base": "ETH",
"counter": "ETH",
"rate": 1
},
"protocol": null,
"isOnHold": false
}
],
"refund": null,
"refunds": []
}
}
```
---
## Send digital asset payouts
This guide outlines the process for integrating crypto payouts into your platform. You can customise your payments page, where you can submit the payout address at the time of payment creation, which automatically completes the payout.
A crypto payout involves transferring cryptocurrency from your account to an end user's crypto wallet.
To accept crypto payments on your platform:
1. Collect the end user's information, including their address and cryptocurrency type.
2. [Initiate a payout and manage the response](/layer1/use-cases/merchant-payments/sending-crypto-payouts/create-a-pay-out-with-payment-out-details).
3. [Set up a Payment Webhook](/layer1/use-cases/merchant-payments/sending-crypto-payouts/listening-for-payout-webhooks) to listen for and handle payments effectively.
By following these steps, you can efficiently integrate crypto payouts into your platform, enhancing your payment offerings with Layer1.
## Payout flow
The payment proceeds through the following stages:
1. The end user requests to make a withdrawal on your system, where you will need to collect the following information:
- Amount for the withdrawal,
- Cryptocurrency
- Protocol (if applicable to the currency)
- Address to which the funds must be sent
2. This information is then forwarded to BVNK as a Create Payment request with `type` set to `OUT`.
3. The [Create Payment](/l1-api/crypto-payments/payment-create) endpoint verifies that the address exists for the specified cryptocurrency and protocol.
4. Layer1 processes the payout and sends out a series of webhooks.
5. Once you receive the final `COMPLETE` webhook, you can poll the Get Payment endpoint to confirm the status and final paid out amount, or take the information from the webhook.
6. Update the end user's account accordingly with the confirmed payment amount.

## Payment lifecycle
The payout undergoes several status changes throughout its lifecycle:
| Status | Immutable? | Description |
| :----------- | :--------- | :---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pending` | No | The initial state of a payout. Indicates that the payment remains in this state until a crypto address is provided or the payout request is cancelled |
| `processing` | No | Indicates that a crypto address is submitted during the processing of the payout |
| `complete` | Yes | Indicates that the payout is completed once funds have been successfully transferred |
| `cancelled` | Yes | Indicates that a payout has been cancelled or failed. Funds stay in the merchant account |
---
## Set up payment processing
The following guide outlines the initial setup required to begin processing payments with the Merchant Payments Engine. These are one-time configurations needed during onboarding.
## Set up webhooks
The Merchant Payments Engine communicates with your Digital Asset and Trading Engine through platform webhooks. They notify your system of events, such as payment status changes and account updates.
To add or update the webhooks, go to **Integrations** > **Webhooks** in the Layer1 Portal.
:::warning
You only have to do this once.
:::
You will need the following codes set up for webhooks.
| URL | Type | Event Codes |
| -------------------------------------- | :--- | :------------------------------------------------------------------------- |
| Speak to your Solutions representative | WEB | `layer1:crypto:deposit:status-change` |
| Speak to your Solutions representative | WEB | `layer1:crypto:withdrawal:status-change` |
| Speak to your Solutions representative | WEB | `layer1:trade:conversion:state-changed` |
| Speak to your Solutions representative | WEB | `layer1:crypto:deposit:status-change` |
| Speak to your Solutions representative | WEB | `layer1:trade:conversion:state-changed` `layer1:trade:conversion:created` |
For more information, see [Webhooks](/l1-api/api-overview/webhooks).
***
## Set up fees
Before processing transactions, you must define at least the **default fee policy**.
To configure fees in the Layer1 Portal, see the [Fee Policy Setup Guide](/layer1/use-cases/merchant-payments/charge-merchant-fees).
***
## Define processing settings
There are some settings you control which apply to all of your merchant processing globally.
- **Default asset pool**: Your merchant payments requires a linked **asset pool** for all merchant activity to flow in and out of. To configure this please visit **Merchant Payments** > **Merchant accounts** > **Processing settings**.
- **Minimum and maximum payment values**: These limits enforce validation within the Payment Links product, ensuring that all payments fall within a range appropriate for your business model. To configure this please visit **Merchant Payments** > **Merchant accounts** > **Processing settings**.
- **Micro payment threshold**: Any digital asset amount received below this threshold will not be made available to your merchants. Instead, the assets will remain in your asset pool. These payments are typically too small to be operationally viable, for example, amounts that cannot be effectively converted at trading venues. Contact your Solutions representative to complete this step.
***
## Create your Merchant Account
Finally, create an account for your merchant to provide them with payment capabilities.
To onboard your first merchant:
1. Log in to the Layer1 Portal.
2. Go to **Merchant Payments** > **Merchant accounts**, and click **Add account**.
3. In the **Add account** form, provide the following details:
- Company name, and optionally the company registration number and country of registration
- Reference
- Network whitelist: allowed IP addresses
- Account owner details: first and last name, email address
4. Click **Add account**.
After the submission, the owner receives an email invite. When they log in to the **Merchant Portal**, they can:
- Invite and manage team members and assign appropriate roles and permissions.
- Create and manage wallets.
- Configure API authentication (FAPI) to initiate payments via API.
## Manage your merchant's wallet settings
Once your merchant has created their wallets in the **Merchant Portal** you will see them in your **Tenant Portal**. Go to **Merchant Payments** > **Merchant accounts** and click the account.
Here you can view your merchant's wallets and balances.
To update a wallet's settings, such as whether the merchant or their customer will pay for the network and payout processing fees, click the edit icon next to the wallet. On the **Fees** tab, click **Edit**.
Now your merchants can process crypto payments. See [Accept digital asset payments](/layer1/use-cases/merchant-payments/building-a-checkout-for-crypto-payments-1) and [Send digital asset payouts](/layer1/use-cases/merchant-payments/sending-crypto-payouts).
---
## Merchant payments
The **Merchant Payment Engine** is a complete, off-the-shelf crypto checkout solution explicitly built for Layer1 tenants. It lets you instantly give your merchants a comprehensive crypto checkout payment suite and more, without any development work. All the merchant-, end-customer-, and tenant-facing components come included as standard.
With this module, merchants can easily accept and manage crypto payments, while tenants can charge fees and provide top-notch support, all powered by the Layer1 platform.
***
## Core Features
### Payment Links
Payment Links, or Crypto Checkout, generate one-time-use payment links that your merchants can share with customers or embed into their platform to accept or send digital assets.
**Key Capabilities**:
- Auto-converts received crypto into the merchant’s preferred wallet currency.
- Supports both payment in and out use cases.
- Ideal for one-time invoice use cases.
- Fully available via [API](/l1-api/crypto-payments/payment-create) (see **Related API endpoints** below).
**Related user guides:**
- [Accept payments](/layer1/use-cases/merchant-payments/building-a-checkout-for-crypto-payments-1)
- [Send crypto payouts](/layer1/use-cases/merchant-payments/sending-crypto-payouts)
***
### Channels
Channels are issue-dedicated, reusable wallet addresses for customers to deposit any amount of digital assets at any time.
**Key Capabilities:**
- Repeat-use wallets for recurring or flexible deposits
- Auto-conversion into the merchant’s preferred currency
- Ideal for customer top-ups use cases
- Accessible via API and the Account Portal
For more details, see the dedicated User Guide.
***
### Conversions
With the Conversion feature, your merchants can convert balances between supported digital assets to manage their treasury.
**Key Capabilities**:
- Instant and transparent conversions between wallets.
- Conversion rates are provided in real time with auto-refresh.
- Accessed via the Account Portal. Go to **Wallets** → **Convert**.
- Wallet transaction export available for accounting needs.
***
### Account Portal
Account Portal is a white-labeled merchant-facing dashboard for complete self-service management.
**Key Capabilities**:
- **Payments**. Track incoming and outgoing transactions.
- **Balances**. View, convert, and manage digital asset holdings.
- **Approvals**. Configure and execute approval rules for payouts.
- **Exports**. Generate CSVs for accounting needs.
- **Team Management**. Add, remove, or edit user permissions.
- **Developer Tools**. View API keys and manage webhooks.
***
### Fee Policy Engine
The Fee Policy Engine is a tenant-facing component that allows you to configure and charge your merchants for the services you provide.
**Key Capabilities**:
- **Default fee policy:** Set default fees applied to all merchants.
- **Merchant fee policies:** Set merchant-specific fees to align with agreements.
- **Fixed and variable:** Set fixed or percentage-based fees across all fee types.
- **Customise per product:** Set different fees across all merchant-facing products.
For more details, see the [Charge Merchant Fees](/layer1/use-cases/merchant-payments/charge-merchant-fees) guide.
***
## Related API endpoints
Merchant Payments primarily uses the Crypto Payments, Channels, Webhooks, and Approvals APIs:
- [Create payment](/l1-api/crypto-payments/payment-create)
- [Create channel](/l1-api/channels/create-channel)
- [Get merchant channel](/l1-api/channels/get-merchant-channel)
- [List merchant channel payments](/l1-api/channels/list-merchant-channel-payments)
- [Get spot rates](/l1-api/channels/get-spot-rates)
- [Create webhook destination](/l1-api/webhooks/create-destination)
- [Submit approval vote](/l1-api/approvals/submit-vote)
---
## Add Ethereum wallet for USDT and USDC
To simulate payment transactions using the Payments API in Sandbox, set up a MetaMask Ethereum wallet and add test tokens to it. This process allows you to experiment with and verify the API's functionality in a test environment.
## Install MetaMask extension
1. [Install Metamask extension](https://metamask.io/download/) for your browser, Chrome is recommended. Follow the steps carefully and save your password and recovery codes in a safe place.
2. Optional. Pin the extension to your browser by clicking the **Puzzle** icon > **Pin** icon next to MetaMask.
3. Click the fox-head icon to open your MetaMask wallet and log in.
4. By default, the Ethereum Mainnet network is selected. To change to another one, click the **Networks** dropdown at the top.
5. The **Select a network** tab opens. Click the slider to show test networks, then select **Sepolia**.
6. The SepoliaETH token name indicates that you are on the Sepolia Network. Now you can receive funds and test Ethereum transactions in the sandbox.
***
## Add USDT ERC20
To add USDT ERC20 tokens to the wallet, do the following:
1. Import tokens by adding the smart contract to your wallet. For that, in your MetaMask wallet, on the **Tokens** tab, click **Import tokens**.
2. The import tokens screen opens. In the **Token contract address** field, add `0x3429519eE7cDbB13B49161F1Eb6e1B026939113A`.
The **Token symbol** and **Token Decimal** are auto-completed. If not, enter **USDT** and **6** respectively.
3. When the prompt appears, click **Import**.
USDT ERC20 is now added to your wallet, and you can complete USDT transactions once you have received test funds.
ETH and USDT ERC20 on the Sepolia Network are now available on your MetaMask wallet. Get some test funds loaded and happy testing!
## Add USDC
To add USDC ETHEREUM tokens to the wallet, do the following:
1. Import tokens by adding the smart contract to your wallet. For that, in your MetaMask wallet, go to the **Tokens** tab and click **Import tokens**.
2. The import tokens screen opens. Configure the fields as follows:
- **Token contract address**: `0x6988F886eC2706893d37eE169722DdB02135eF78`
- **Token symbol**: USDC
Click **Next**.
3. When the prompt appears, click **Import**.
USDC ETHEREUM is now added to your wallet, and you can complete USDC transactions once you have received test funds.
---
## Add TronLink wallet for TRX and USDT
To simulate payment transactions using the Payments API in Sandbox, set up a TronLink wallet and add test tokens to it. This process allows you to experiment with and verify the API's functionality in a test environment.
## Install TronLink extension
1. Go to [Chrome Web Store](https://chromewebstore.google.com/detail/tronlink/ibnejdfjmmkpcnlpebklmnkoeoihofec), and on the TronLink extension page, click **Add to Chrome**.
2. Optional. Pin the extension to your browser by clicking the **Puzzle** icon > **Pin** icon next to TronLink.
3. Click the **TronLink** icon to open the extension, and then click **Create Wallet**. Follow the steps carefully and save your password and recovery codes in a safe place.
4. By default, the Tron Mainnet network is selected. To change to another one, click the **Networks** dropdown at the top.
5. The **Networks** tab opens. Select **TRON Nile Testnet**.
The **TRON Nile Testnet** at the top indicates you are now on the Nile TestNet. Now you can receive funds and test transactions in the sandbox.
***
## Add TRON to your wallet
To activate your TronLink account, add TRON test coins to it. For that,
1. In the TronLink extension, copy the wallet address.
2. Go to [Nile Testnet](https://nileex.io/join/getJoinPage) and enter your TronLink wallet address.
2000 test Tron coins will be sent to your wallet.
## Add LAYER1 USDT TRC20
To add USDT TRC20 tokens to your TronLink wallet, import the tokens created by BVNK by adding the smart contract to your wallet.
To add a smart token, do the following:
1. Go to [Tronscan](https://nile.tronscan.org/), and click **Connect Wallet** on the top right.
2.
In the **Search** bar, enter `TY1DBj7Ys1bDcK37kwATaQpHxdTCnYrr1f`.
The **LAYER1** token appears. This is a BVNK test USDT token that can be used as a USDT TRC20.
3.
Select this token.
4. Go to the **Contract** tab > **Write Contract**, and select **mint**.
Enter the amount of tokens to receive (for example, 50.000), and click **Send**.
5. The **Trigger Smart Contract** window may appear. Click **Sign**.
6. Once the transaction has gone through, open your TronLink extension in Chrome, and next to **Assets**, click the "**+**" sign.
7. In the **Assets** list, in the **New** section, select **LAYER1**, and click the "**+**" sign.
You now have BVNK LAYER 1 (USDT TRC20) Testnet tokens that can be used in transactions via API and the BVNK portal for both payments in and out.
You've successfully acquired BVNK LAYER 1 (USDT TRC20) Testnet tokens. These tokens are designed for testing purposes and can be utilised in various ways:
- Simulate transactions through the API.
- Test payment flows within the BVNK Portal.
- Experiment with both incoming and outgoing payments.
This allows you to thoroughly evaluate the platform's functionality in a risk-free environment before engaging with real assets.
---
## Test blockchain integration
Currently, Layer1 is integrated with the Sepolia test network for Ethereum and the Nile test network for Tron. Set up a wallet for receiving and sending test coins, then fund it based on the asset and network:
| Network | Asset | Wallet | How to fund |
| :--------------- | :--------- | :----- | :---------- |
| Ethereum Sepolia | ETH | [MetaMask](/layer1/use-cases/testing-your-integration/setting-up-a-metamask-ethereum-wallet-for-eth-and-usdt-erc20-tokens) | Use the [Sepolia faucet](https://sepoliafaucet.com/), or mine some using the [Sepolia Proof of Work (PoW) Faucet](https://sepolia-faucet.pk910.de/). |
| Ethereum Sepolia | USDT ERC20 | [MetaMask](/layer1/use-cases/testing-your-integration/setting-up-a-metamask-ethereum-wallet-for-eth-and-usdt-erc20-tokens#add-usdt-erc20) | Request tokens through your integration manager, as BVNK operates its smart contract for this purpose. |
| Tron Nile | TRX | [TronLink](/layer1/use-cases/testing-your-integration/setting-up-a-tronlink-wallet-for-trx-and-usdt-trc20-tokens) | Obtain coins from the [Nile Tron page](https://nileex.io/join/getJoinPage). |
| Tron Nile | USDT TRC20 | [TronLink](/layer1/use-cases/testing-your-integration/setting-up-a-tronlink-wallet-for-trx-and-usdt-trc20-tokens#add-layer1-usdt-trc20) | Use the LAYER1 token on the `TY1DBj7Ys1bDcK37kwATaQpHxdTCnYrr1f` contract, or contact your integration manager. |
If you have any problems, your integration manager can assist you in funding your wallet.
---
## Understanding the conversion flow
This section describes the lifecycle of a conversion and order on the Layer1 platform, outlining the different statuses a conversion goes through from initiation, when it it first created by API request to a trading venue, to completion where your balances in a currency will be converted to another.
## Conversion States
A Conversion will go through a series of statuses in the conversion workflow, with each status denoting a specific process has occurred:
| **Status** | **Description** | Final state |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | :---------- |
| **`PENDING`** | The conversion request is first created with the pending status | No |
| **`PROCESSING`** | The conversion order has been sent to trading venue(s) | No |
| **`COMPLETED`** | The successfully converted amount will be marked as completed. Any not completed will generate new orders in the placed status. This is a final state | Yes |
| **`PENDING_CANCEL`** | A cancelled conversion will first transition into a pending cancel state when the cancellation has been requested | No |
| **`CANCELLED`** | The cancelled orders will be marked as cancelled. This is a final state | Yes |
| **`FAILED`** | The conversion had no successfully converted amount. This is a final state | Yes |
## Order States
An order will go through a series of statuses in the conversion workflow, with each status denoting a specific process has occurred:
| **Status** | **Description** | Final state |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | :---------- |
| **`PENDING`** | The order created (from the conversion created) will start with the pending status. Order is awaiting submission to the trading venue | No |
| **`PROCESSING`** | The order has been submitted to trading venue | No |
| **`COMPLETED`** | The order is filled. This is a final state | Yes |
| **`FAILED`** | The order was unsuccessful with no financial impact. This is a final state | Yes |
| **`PENDING_CANCEL`** | A cancelled order or expired order will first transition into a pending cancel status | No |
| **`CANCELLED`** | The order has been cancelled with the trading venue. This is a final state | Yes |
Now that you understand the different statuses a conversion and order goes through, you can issue your first conversion either via API or in the Layer1 Portal.
---
## Convert balances
Using Layer1 you can convert balances held at your connected trading venues. With Layer1's API connections to the venues you are able to optimise conversion by having them placed across multiple venues to achieve best order execution.
Layer1 has several concepts that you will see throughout the platform:
| Concept | Description |
| :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Conversions** | A conversion represents a single transaction on the Layer1 UI that maps a currency exposure (for example, converting EUR to USDC). It is the highest-level entity in this hierarchy and serves as the starting point for the trading process. |
| **Legs** | Each conversion can consist of one or multiple "legs." A leg represents a segment of the conversion process, such as splitting the transaction into smaller parts for execution (for example, EUR to BTC, BTC to USDC) |
| **Orders** | Each leg can have multiple orders. An order is a specific instruction sent to a trading venue (for example, Kraken, Coinbase) to execute a part of the conversion. Orders are created based on the conversion's requirements and routed to the appropriate venues for execution. |
| **Trades** | Each order can result in multiple trades. Trades are the actual executions that occur on the trading venue, fulfilling the order's requirements. For example, an order to buy 10,000 USDC might be fulfilled through multiple smaller trades on the venue for more optimised execution. |
| **Movements** | The sum of all trades at the asset level is referred to as "movements." This represents the final financial impact of the trades in terms of your asset balances. |
Next, see [Understanding the conversion flow](/layer1/use-cases/trading/converting-or-trading-your-balances/understanding-the-conversion-flow).
---
## Add trading venues
Layer1 integrates with trading venues via the [XChange Java library](https://github.com/knowm/XChange). This provides consistent access to major exchanges through a unified interface, enabling seamless trading, quoting, and balance management workflows.
You can select which venues to enable on the Layer1 Portal, via **Integrations** > **Connected providers**.
## Supported venues
| Exchange | XChange Class | Asset Types Supported |
| -------- | ------------------ | --------------------- |
| Binance | `BinanceExchange` | Spot, Futures |
| Kraken | `KrakenExchange` | Spot, Futures |
| Bitstamp | `BitstampExchange` | Spot |
:::note
If the trading venue you wish to connect to does not appear in the supported providers list, reach out to your account executive for more information and a possible solution.
:::
## Add a trading venue
Before adding a venue to your Layer1 account, ensure you have obtained the corresponding API key and secret.
To add a trading venue, do the following:
1. Log in to the Layer1 Portal.
2. In the side menu, go to **Integrations** > **Connected providers**, and click **Add Provider**.
3. Select **Trade**, select the required **Provider** from the menu, then complete the following fields:
- **Display name:** Enter the name you want to give this trading venue account on Layer1.
- **Alias:** Enter a unique identifier for each trading API key. Choose a name that reflects the purpose/location of the key to make it easily recognisable, for example `eu-entity1`, `spain-entity1`.
- **Api Secret:** Enter the API secret from the trading venue you're connecting.
- **Api Key:** Enter the API key from the trading venue you're connecting.
4. Click **Add provider**.
The new connection appears in the **Connected providers** list. Note that it can take up to one business day to initialise your connection with your trading venue.
## Configure trading symbols
To access market data and execute trades from your Layer 1 wallet, you must first configure the specific trading symbols, in which you're interested.
To enable symbols for trading, do the following:
1. **Connect a trading venue** by following the steps [above](/layer1/use-cases/trading/trading-venues#add-a-trading-venue).
2. Go to **Trading** > **Venues** and click **Symbol Configuration**.
3. Find the symbols for your venue account. You can search by symbol (for example, `TRX-USD`) and filter the list by venue and status.
4. Click the **Enabled** toggle next to each symbol you want to enable.
By default, all symbols for a venue account are disabled.
To turn off symbols for trading, click the **Enabled** toggle to the off position.
The changes may require up to 10 minutes to take effect.
---
## Trading
---
## Manage balances
In Layer1, a managed balance wallet is always a master wallet.
## List all managed balances
To **list all managed balances**, send a [`GET /treasury/v1/managed-balances`](/l1-api/treasury/get-all-managed-balances) request.
In the response, you receive the complete list of detailed balances:
Response
```json title="Example Response"
{
"content": [
{
"id": "bvnk_tron_usdt",
"name": "Main USDT Wallet",
"remoteWalletMetadata": {
"balanceId": "550e8400-e29b-41d4-a716-446655440000",
"addressId": "550e8400-e29b-41d4-a716-446655440000",
"address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"assetPoolId": "550e8400-e29b-41d4-a716-446655440000",
"network": "TRON",
"tag": "12345",
"type": "LAYER1_MASTER_ADDRESS"
},
"eligibleSourcesAndIntermediaries": {
"bvnk_ethereum_usdt": [
"bvnk_tron_usdt"
],
"bvnk_kraken_btc": []
},
"thresholds": {
"minBalance": 500000,
"warningBalance": 800000,
"targetBalance": 1000000,
"minBalanceWindowWidth": 2,
"warningBalanceWindowWidth": 3,
"targetBalanceWindowWidth": 6,
"minBalanceBufferFactor": 0.2,
"transferOperationHardTimeout": "PT1H"
},
"monitoringEnabled": true,
"forecastEnabled": true
}
],
"totalElements": 1,
"totalPages": 1,
"pageNumber": 0,
"pageSize": 50
}
```
***
## Create a managed balance
To **create a managed balance**, send a [`POST /treasury/v1/managed-balances`](/l1-api/treasury/create-managed-balance) request with JSON body:
```json title="Body parameters"
{
"entityCode": "kraken",
"asset": "USDT",
"name": "Main Bitcoin Wallet",
"remoteWalletMetadata": {
"balanceId": "550e8400-e29b-41d4-a716-446655440000",
"addressId": "550e8400-e29b-41d4-a716-446655440000",
"address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"assetPoolId": "550e8400-e29b-41d4-a716-446655440000",
"network": "BITCOIN",
"tag": "12345",
"type": "LAYER1_MASTER_ADDRESS"
},
"thresholds": {
"minBalance": 500000,
"warningBalance": 800000,
"targetBalance": 1000000,
"minBalanceWindowWidth": 2,
"warningBalanceWindowWidth": 3,
"targetBalanceWindowWidth": 6,
"minBalanceBufferFactor": 0.2,
"transferOperationHardTimeout": "PT1H"
},
"monitoringDisabled": false,
"forecastDisabled": false
}
```
:::note
Thresholds are denominated in the asset of the wallet. Here, in USDT (as in the `asset` field).
:::
In the response, you receive the detailed information on the new managed balance:
Response
```json title="Example Response"
{
"id": "bvnk_tron_usdt",
"name": "Main USDT Wallet",
"remoteWalletMetadata": {
"balanceId": "550e8400-e29b-41d4-a716-446655440000",
"addressId": "550e8400-e29b-41d4-a716-446655440000",
"address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"assetPoolId": "550e8400-e29b-41d4-a716-446655440000",
"network": "TRON",
"tag": "12345",
"type": "LAYER1_MASTER_ADDRESS"
},
"eligibleSourcesAndIntermediaries": {
"bvnk_ethereum_usdt": [
"bvnk_tron_usdt"
],
"bvnk_kraken_btc": []
},
"thresholds": {
"minBalance": 500000,
"warningBalance": 800000,
"targetBalance": 1000000,
"minBalanceWindowWidth": 2,
"warningBalanceWindowWidth": 3,
"targetBalanceWindowWidth": 6,
"minBalanceBufferFactor": 0.2,
"transferOperationHardTimeout": "PT1H"
},
"monitoringEnabled": true,
"forecastEnabled": true
}
```
***
## Get details of a specific managed balance
To **retrieve details of a specific managed balance**, send a
[`GET /treasury/v1/managed-balances/{managedBalanceId}`](/l1-api/treasury/get-managed-balance) request with path parameter:
In the response, you receive the detailed information on the wallet:
Response
```json title="Example Response"
{
"id": "bvnk_tron_usdt",
"name": "Main USDT Wallet",
"remoteWalletMetadata": {
"balanceId": "550e8400-e29b-41d4-a716-446655440000",
"addressId": "550e8400-e29b-41d4-a716-446655440000",
"address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"assetPoolId": "550e8400-e29b-41d4-a716-446655440000",
"network": "TRON",
"tag": "12345",
"type": "LAYER1_MASTER_ADDRESS"
},
"eligibleSourcesAndIntermediaries": {
"bvnk_ethereum_usdt": [
"bvnk_tron_usdt"
],
"bvnk_kraken_btc": []
},
"thresholds": {
"minBalance": 500000,
"warningBalance": 800000,
"targetBalance": 1000000,
"minBalanceWindowWidth": 2,
"warningBalanceWindowWidth": 3,
"targetBalanceWindowWidth": 6,
"minBalanceBufferFactor": 0.2,
"transferOperationHardTimeout": "PT1H"
},
"monitoringEnabled": true,
"forecastEnabled": true
}
```
***
## Update an existing managed balance
To **update the parameters of an existing managed balance** (for example, change thresholds or source of funds), send a [`PUT /treasury/v1/managed-balances/{managedBalanceId}`](/l1-api/treasury/upsert-managed-balance) request with balance's `managedBalanceId` in the path and the following body parameters:
```json title="Body parameters"
{
"name": "Main Bitcoin Wallet",
"remoteWalletMetadata": {
"balanceId": "550e8400-e29b-41d4-a716-446655440000",
"addressId": "550e8400-e29b-41d4-a716-446655440000",
"address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"assetPoolId": "550e8400-e29b-41d4-a716-446655440000",
"network": "BITCOIN",
"tag": "12345",
"type": "LAYER1_MASTER_ADDRESS"
},
"thresholds": {
"minBalance": 500000,
"warningBalance": 800000,
"targetBalance": 1000000,
"minBalanceWindowWidth": 2,
"warningBalanceWindowWidth": 3,
"targetBalanceWindowWidth": 6,
"minBalanceBufferFactor": 0.2,
"transferOperationHardTimeout": "PT1H"
},
"monitoringDisabled": false,
"forecastDisabled": false
}
```
In the response, you receive the updated information on the managed balance.
Response
```json title="Example Response"
{
"id": "bvnk_tron_usdt",
"name": "Main USDT Wallet",
"remoteWalletMetadata": {
"balanceId": "550e8400-e29b-41d4-a716-446655440000",
"addressId": "550e8400-e29b-41d4-a716-446655440000",
"address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"assetPoolId": "550e8400-e29b-41d4-a716-446655440000",
"network": "TRON",
"tag": "12345",
"type": "LAYER1_MASTER_ADDRESS"
},
"eligibleSourcesAndIntermediaries": {
"bvnk_ethereum_usdt": [
"bvnk_tron_usdt"
],
"bvnk_kraken_btc": []
},
"thresholds": {
"minBalance": 500000,
"warningBalance": 800000,
"targetBalance": 1000000,
"minBalanceWindowWidth": 2,
"warningBalanceWindowWidth": 3,
"targetBalanceWindowWidth": 6,
"minBalanceBufferFactor": 0.2,
"transferOperationHardTimeout": "PT1H"
},
"monitoringEnabled": true,
"forecastEnabled": true
}
```
***
## Update thresholds for a managed balance
To **update thresholds only**, send a [`PATCH /treasury/v1/managed-balances/{managedBalanceId}/thresholds`](/l1-api/treasury/patch-thresholds) request with the balance's `managedBalanceId` in the path and the following body parameters:
```json title="Body parameters"
{
"minBalance": 12000,
"warningBalance": 18000,
"targetBalance": 60000,
"minBalanceBufferFactor": 1.5
}
```
In the response, you receive the updated information on the wallet:
Response
```json title="Example Response"
{
"id": "bvnk_tron_usdt",
"name": "Main USDT Wallet",
"remoteWalletMetadata": {
"balanceId": "550e8400-e29b-41d4-a716-446655440000",
"addressId": "550e8400-e29b-41d4-a716-446655440000",
"address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"assetPoolId": "550e8400-e29b-41d4-a716-446655440000",
"network": "TRON",
"tag": "12345",
"type": "LAYER1_MASTER_ADDRESS"
},
"eligibleSourcesAndIntermediaries": {
"bvnk_ethereum_usdt": [
"bvnk_tron_usdt"
],
"bvnk_kraken_btc": []
},
"thresholds": {
"minBalance": 500000,
"warningBalance": 800000,
"targetBalance": 1000000,
"minBalanceWindowWidth": 2,
"warningBalanceWindowWidth": 3,
"targetBalanceWindowWidth": 6,
"minBalanceBufferFactor": 0.2,
"transferOperationHardTimeout": "PT1H"
},
"monitoringEnabled": true,
"forecastEnabled": true
}
```
***
## Remove a managed balance
To **delete a managed balance**, send a [`DELETE /treasury/v1/managed-balances/{managedBalanceId}`](/l1-api/treasury/delete-managed-balance) request with path parameter:
Successful deletion returns HTTP `204 No Content`.
---
## Manage funding sources
## Add a funding source to a managed balance
To **add a funding source to a managed balance**, send a [`PUT /treasury/v1/managed-balances/{managedBalanceId}/eligible-sources/{sourceManagedBalanceId}`](/l1-api/treasury/upsert-eligible-source) request with the following path parameters: the target managed balance ID (for example, `ag_layer1_usdt`) and the source managed balance ID (for example, `ag_kraken_usdt`).
In the request body, specify the following parameters:
```json title="Example Request"
{
"intermediaries": [
"bvnk_ethereum_usdt"
],
"priority": 2
}
```
Here,
- `intermediaries` are bridge services (such as exchanges or managed balances) used when direct transfers aren’t possible—especially across networks. For example, to move USDT from Tron to Ethereum, an intermediary like Kraken can receive the funds on one chain and issue them on another. They can also be used on the same network for routing, compliance, or control.
- `priority` is a numerical ranking of eligible sources: `1` is tried first, then `2`, and so on. Use it to select a source when multiple sources have sufficient balance to fulfil the transfer.
In the successful response, you receive the updated managed balance information.
```json title="Example Response"
{
"id": "bvnk_tron_usdt",
"name": "Main USDT Wallet",
"remoteWalletMetadata": {
"balanceId": "550e8400-e29b-41d4-a716-446655440000",
"addressId": "550e8400-e29b-41d4-a716-446655440000",
"address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"assetPoolId": "550e8400-e29b-41d4-a716-446655440000",
"network": "TRON",
"tag": "12345",
"type": "LAYER1_MASTER_ADDRESS"
},
"eligibleSourcesAndIntermediaries": {
"bvnk_ethereum_usdt": [
"bvnk_tron_usdt"
],
"bvnk_kraken_btc": []
},
"thresholds": {
"minBalance": 500000,
"warningBalance": 800000,
"targetBalance": 1000000,
"minBalanceWindowWidth": 2,
"warningBalanceWindowWidth": 3,
"targetBalanceWindowWidth": 6,
"minBalanceBufferFactor": 0.2,
"transferOperationHardTimeout": "PT1H"
},
"monitoringEnabled": true,
"forecastEnabled": true
}
```
***
## Retrieve all funding sources
To **get a list of all eligible sources** with their details and priorities for a managed balance, send a [`GET /treasury/v1/managed-balances/{managedBalanceId}/eligible-sources`](/l1-api/treasury/get-eligible-sources) request with the `managedBalanceId` parameter in the path.
The successful response returns the complete list of eligible sources with their priority and intermediaries.
```json title="Example Response"
[
{
"sourceId": "bvnk_tron_usdt",
"priority": 1,
"intermediaries": [
"bvnk_tron_usdt"
]
},
{
"sourceId": "bvnk_ethereum_usdt",
"priority": 2,
"intermediaries": [
"Kraken"
]
}
]
```
## Remove a funding source from a managed balance
To **remove a funding source from a managed balance**, send a [`DELETE /treasury/v1/managed-balances/{managedBalanceId}/eligible-sources/{sourceManagedBalanceId}`](/l1-api/treasury/remove-eligible-source) request with the following path parameters:
Successful removal returns HTTP `204 No Content`.
## Use case example
Morgan, head of treasury at TranStar, spotted a warning on her dashboard: the company’s wallet (`bvnk_tron_usdt`) was running low. This wallet funded daily payouts across Southeast Asia, and any delay could halt operations on the ground.
She quickly checked available balances. Their `bvnk_ethereum_usdt` wallet had more than enough USDT—but TRON and Ethereum are separate networks. A direct transfer wasn’t possible.
Morgan used Layer1’s intermediary feature to solve it. She configured `bvnk_ethereum_usdt` as a **funding source** for `bvnk_tron_usdt` and listed `Kraken` as the **intermediary**. That told the system to:
- Pull funds from Ethereum.
- Route them through the `Kraken` exchange or internal liquidity path.
- Deliver them as USDT on the TRON network.
Within minutes, the balance was topped up, and payments resumed.
Morgan smiled. No panic, no delays—just smart routing, exactly when it was needed.
---
## Predict top-ups
Forecasting is a Smart Treasury feature that uses historical transaction data to predict upcoming fund needs for your wallets. It helps ensure your funds are in the right place at the right time, thus reducing the risk of unnecessary borrowing or failed transactions.
## Overview
Forecasting uses machine learning to analyse daily and weekly transaction trends in each wallet. It anticipates future cash flow needs based on your past activity. Note that forecasting accuracy and reliability heavily depend on the quality and quantity of historical data available. For machine learning models to produce meaningful and accurate predictions, they require a substantial amount of relevant historical information.
After enabling the Forecasting feature, the system compares forecasted payments for the next X hours. This time window is defined in the `minBalanceWindowWidth` / `targetBalanceWindowWidth` parameters.
Based on this static and dynamic data, the feature can make predictions and dynamically change the thresholds.
When a wallet is projected to dip below safe levels, the system:
1. Identifies which source wallets (e.g. master wallets or exchanges) have available funds.
2. Selects the most efficient source based on balance and simplicity of transfer.
3. Triggers the withdrawal and sends a notification about it.
4. Uses the Level1 workflow to request approval via API or Portal:
- If static thresholds are used, no approval is needed.
- If forecasting-based logic triggers the adjustment, approval is requested via the Portal or API.
Once approved, funds are transferred; conversion is automatically performed if the source currency is different.
The operation is automatically and safely cancelled if the system receives no approval within a specific timeframe.
## Get forecasts
1. Update the current thresholds and enable Forecasting, send the [`PUT /treasury/v1/managed-balances/{managedBalanceId}` ](/l1-api/treasury/upsert-managed-balance) request with the following body parameters:
```json title="Example Request"
"thresholds": {
"minBalance": 500000,
"warningBalance": 800000,
"targetBalance": 1000000,
"minBalanceWindowWidth": 2,
"warningBalanceWindowWidth": 3,
"targetBalanceWindowWidth": 6,
"minBalanceBufferFactor": 0.2,
"transferOperationHardTimeout": "PT1H"
},
"forecastDisabled": false
```
Here,
- In [`thresholds`](/layer1/use-cases/treasury/automate-treasury-top-ups/quick-start#update-thresholds), specify the following:
- `minBalance` / `targetBalance`: Desired minimum and target balance.
- `minBalanceBufferFactor`: Offset to always keep a specific amount of funds.
- `minBalanceWindowWidth` / `targetBalanceWindowWidth`: Time window in hours to be analysed for forecasts.
- `warningBalanceWindowWidth`: Time window that specifies how far into the future the system checks your forecasted balance before deciding to raise an alert. For example, if set to `2` hours, the system projects your balance two hours ahead. If that forecast falls below your `warningBalance` threshold, an alarm will be issued.
- Set `forecastDisabled` to `false` to enable forecasting.
- Specify eligible funding wallets/exchanges; the model will rank them automatically. The model will:
- Prefer same-currency sources.
- Evaluate conversion cost when a currency swap is unavoidable.
- Avoid sources whose own forecast shows they’ll dip too low.
- Metadata: set up once.
2. [Configure webhooks ](/layer1/use-cases/treasury/automate-treasury-top-ups/receive-notification) for forecast alerts & approval prompts.
Use the hook service to get alerts:
- When forecasting detects a low balance
- When a transfer is triggered or fails
- When threshold updates or approvals are needed
3. Approve requests through API or Portal. Unapproved requests time out and are safely cancelled:
- In Portal: click **Approve** / **Reject**.
- Via API: send the [`POST digital/v1/screenings/action`](/l1-api/digital-assets/manually-action-transfer) request with transfer ID and the destination address ID.
4. Monitor update thresholds anytime; the model continually improves with every transaction.
## Example use case
Emily, a treasury manager at the Dunwall fintech company, had set a minimum balance threshold of 50,000 USD for their Ethereum wallet used for customer payouts.
One Monday, the wallet held 53,000 USD. The Forecasting system predicted 10,000 USD would be spent in the next 12 hours. Despite the relatively low forecasted spend, the system aimed to maintain the 50,000 USD safety buffer.
It evaluated the available source wallets:
- Kraken: 6,000 USD
- Binance: 120,000 USD, but takes more steps to execute
- Tron wallet: 84,000 USD and fast
Forecasting chose the Tron wallet due to its high balance and minimal transfer steps. It triggered a 12,000 USD top-up request, notified Emily in Slack, and sent the request for approval via the Portal. Emily approved the request, and the system executed the transfer. The conversion from Tron to Ethereum was handled automatically.
Shortly after, the Ethereum wallet was topped up, and Emily received a notification:
> “Top-up from Tron wallet completed. 12,000 USD was added to the Ethereum wallet to maintain the threshold.”
---
## Quick start
Use the Smart Treasury API to automate wallet funding and ensure constant liquidity without manual intervention. This guide walks you through three key steps to get started:
1. [Add a managed balance](/layer1/use-cases/treasury/automate-treasury-top-ups/quick-start#add-a-managed-balance): define the wallet you want to automate and create your liquidity sources.
2. [Configure thresholds](/layer1/use-cases/treasury/automate-treasury-top-ups/quick-start#update-thresholds): set rules for triggering top-ups.
3. [Assign eligible funding sources](/layer1/use-cases/treasury/automate-treasury-top-ups/quick-start#assign-eligible-funding-sources): link liquidity providers like Kraken or Binance.
## Get started with Auto Treasury
Let's configure your first Smart Treasury rule:
- I want to **set up auto-treasury** for my TRX balance because TRX is the currency most frequently withdrawn by customers. The balance often becomes depleted in the middle of the night or over weekends, leading to alerts for our treasury team.
- The **minimum balance** of the account should be around $5k or 15,000 TRX, and the target balance should be around $50k or 150,000 TRX.
- The **liquidity sources** should be:
- My TRX balance at Kraken
- My USDT balance at Kraken
- The **intermediary** should be my TRX balance at Kraken.
### Recommended approach. Create an auto top-up source first
First, create a managed balance for the TRX balance in your Layer1 wallet, for which you set up auto top-ups. As you haven't created your liquidity sources yet, you won't configure them yet.
You'll define the wallet, its asset, metadata, and threshold rules. To set up this auto top-up source, do the following:
1. Send a [`POST /treasury/v1/managed-balances`](/l1-api/treasury/create-managed-balance) request with the following body:
```json title="Example request"
{
"entityCode": "tron",
"asset": "TRX",
"name": "TRX master balance",
"remoteWalletMetadata": {
"balanceId": "5e243653-6dae-4665-bd2c-34533760744f",
"addressId": "dd49b938-ed92-49a8-a82b-6ed0ee6ca974",
"address": "TWNQ5Y5jXzH24fTd4c9DkLqV3mSy1WBMSp",
"assetPoolId": "d6f49aa3-ff64-4bcd-9a5e-93219d73634a",
"network": "TRON",
"type": "LAYER1_MASTER_ADDRESS"
},
"thresholds": {
"minBalance": 15000,
"warningBalance": 30000,
"targetBalance": 150000,
"minBalanceWindowWidth": 2,
"warningBalanceWindowWidth": 3,
"targetBalanceWindowWidth": 6,
"minBalanceBufferFactor": 0.2,
"transferOperationHardTimeout": "PT1H"
},
"monitoringDisabled": false,
"forecastDisabled": false
}
```
2. Create a managed balance for each of your liquidity sources.
You can start with your TRX balance at Kraken. To do this, send a [`POST /treasury/v1/managed-balances`](/l1-api/treasury/create-managed-balance) request with the following body:
```json title="Example request"
{
"entityCode": "kraken",
"asset": "TRX",
"name": "Kraken TRX balance",
"remoteWalletMetadata": {
"type": "LAYER1_TRADE_VENUE",
"blockchainAddresses": [
{
"network": "TRON",
"address": "TPYmHEhy5n8TCEfYGqW2rPxsghSfzghPDn"
}
]
},
"thresholds": {
"minBalance": 15000,
"targetBalance": 150000,
"minBalanceBufferFactor": 0.2
},
"monitoringDisabled": false,
"forecastDisabled": false
}
```
In the response, you receive the ID for the managed balance, for example `kraken_tron_trx`. Save it, as you will need this value later.
3. Repeat step 2 for your USDT balance at Kraken.
After this procedure, you have the following liquidity sources IDs:
- `kraken_tron_trx`
- `kraken_tron_usdt`
4. Link the IDs to your auto top-up balance by sending a `PUT /treasury/v1/managed-balances/{managedBalanceId}/eligible-sources/{sourceManagedBalanceId}` request.
In the request, specify your TRX balance (`layer1_tron_trx`) as the `{managedBalanceId}` and the relevant liquidity source ID, for example, `PUT /treasury/v1/managed-balances/layer1_tron_trx/eligible-sources/kraken_tron_usdt`.
Also, in the request body, you can specify the priority of the source and the intermediaries, for example:
```json title="Example request"
{
"intermediaries": [
"kraken_tron_trx"
],
"priority": 2
}
```
Now, you are set up and ready to go. Your auto-top-ups will start processing automatically once the minimum balance is reached.
### Alternative approach
Alternatively, you can create liquidity sources in the first place:
1. Create a managed balance for each of your liquidity sources, for example, ETH balance at Kraken. To do this, send a `POST /treasury/v1/managed-balances` request with the following body:
```json title="Example request"
{
"entityCode": "kraken",
"asset": "ETH",
"name": "Kraken ETH balance",
"remoteWalletMetadata": {
"type": "LAYER1_TRADE_VENUE",
"blockchainAddresses": [
{
"network": "ETHEREUM",
"address": "0x0a36d17a33e3cb48f3ffc8bc0ae20b9927f087ab"
}
]
},
"thresholds": {
"minBalance": 10,
"targetBalance": 50,
"minBalanceBufferFactor": 0.2
},
"monitoringDisabled": false,
"forecastDisabled": false
}
```
In the response, you receive the `id` for the managed balance, for example, `kraken_ethereum_eth`.
2. Repeat step 1 for your ETH balance at Binance and the USDT balance at Kraken.
3. Create a managed balance for the ETH balance in your Layer1 Wallet, for which you initiate auto top-ups. Additionally, you can configure the liquidity sources in the priority order you have set up for this balance.
To do this, send a `POST /treasury/v1/managed-balances` request with the following body:
```json title="Example request"
{
"entityCode": "ethereum",
"asset": "ETH",
"name": "ETH master balance",
"remoteWalletMetadata": {
"balanceId": "b2bc545f-3b5a-4003-8e5b-bb1d5a20a70e",
"addressId": "6995fc92-adc4-42a5-83b2-06224d731f8f",
"address": "0x0a36d17a33e3cb48f3ffc8bc0ae20b9927f087fe",
"assetPoolId": "d6f49aa3-ff64-4bcd-9a5e-93219d73634a",
"network": "ETHEREUM",
"type": "LAYER1_MASTER_ADDRESS"
},
"thresholds": {
"minBalance": 12,
"warningBalance": 20,
"targetBalance": 47,
"minBalanceWindowWidth": 2,
"warningBalanceWindowWidth": 3,
"targetBalanceWindowWidth": 6,
"minBalanceBufferFactor": 0.2,
"transferOperationHardTimeout": "PT1H"
},
"monitoringDisabled": false,
"forecastDisabled": false,
"eligibleSources": [
{
"sourceManagedBalanceId": "kraken_ethereum_eth",
"intermediaries": [],
"priority": 1
},
{
"sourceManagedBalanceId": "binance_ethereum_eth",
"intermediaries": [],
"priority": 2
},
{
"sourceManagedBalanceId": "kraken_ethereum_usdt",
"intermediaries": [
"kraken_ethereum_eth"
],
"priority": 3
}
]
}
```
Now, you are set up and ready to go. Your auto-top-ups will start processing automatically once the minimum balance is reached.
## Add a managed balance
A managed balance is a wallet configured for automatic monitoring and top-ups. You’ll define the wallet, its asset, metadata, and threshold rules. To add a managed balance, send a [`POST /treasury/v1/managed-balances`](/l1-api/treasury/create-managed-balance) request.
The system supports two wallet types:
- `LAYER1_MASTER_ADDRESS`: A standard Layer1 cryptocurrency master address that consolidates all incoming deposits and serves as the primary source for withdrawals.
- `LAYER1_TRADE_VENUE`: your balance in a connected third-party exchange, such as Binance or Kraken.
In this request, you must also specify `thresholds` to trigger notifications or top-ups. They determine when and how balance adjustments occur and are set in the native asset of the managed balance wallet.
Use the following request body for the wallet type you are adding:
```json title="Example request"
{
"entityCode": "ethereum",
"asset": "USDT",
"name": "USDT master balance",
"remoteWalletMetadata": {
"address": "0x5a1b3c9d7e2f4a6b8c0d1e3f5a7b9c1d3e5f7a9b",
"assetPoolId": "d6f49aa3-ff64-4bcd-9a5e-93219d73634a",
"network": "ETHEREUM",
"type": "LAYER1_MASTER_ADDRESS"
},
"thresholds": {
"minBalance": 500000,
"targetBalance": 1000000
},
"monitoringDisabled": false,
"forecastDisabled": false
}
```
```json title="Example request"
{
"entityCode": "kraken",
"asset": "USDT",
"remoteWalletMetadata": {
"type": "LAYER1_TRADE_VENUE",
"managedBalanceId": "bg_kraken_usdt",
"blockchainAddresses": [
{
"network": "ETHEREUM",
"address": "0x8c56761d82f8df396567697a39625564e8306f7c"
}
]
},
"thresholds": {
"minBalance": 20000,
"warningBalance": 30000,
"targetBalance": 100000
},
"monitoringDisabled": false,
"forecastDisabled": false
}
```
## Update thresholds
Thresholds define when the system should raise alerts or trigger balance top-ups. To update an existing managed balance’s thresholds, send the [`PATCH /treasury/v1/managed-balances/{managedBalanceId}/thresholds`](/l1-api/treasury/patch-thresholds) request with the updated threshold parameters in the request body:
```json title="Example request"
{
"minBalance": 5000,
"warningBalance": 10000,
"targetBalance": 25000,
"minBalanceBufferFactor": 0.2
}
```
**Threshold fields:**
- `minBalance`: When the balance falls below this amount, a top-up is triggered.
- `warningBalance`: Alert zone before hitting minimum.
- `targetBalance`: The desired balance level after the top‑up.
- `minBalanceBufferFactor`: A safety margin to ensure funding sources have sufficient liquidity. For example, `0.2` means a source must hold 20% more than the withdrawal amount before it is used.
## Assign eligible funding sources
To enable automated top-ups, you must assign one or more eligible sources that can fund the wallet. For each managed balance, add eligible funding sources (for example, Kraken) by sending a [`PUT /treasury/v1/managed-balances/{managedBalanceId}/eligible-sources/{sourceManagedBalanceId}`](/l1-api/treasury/upsert-eligible-source) request with the request body:
```json title="Example request"
{
"intermediaries": [
"bvnk_ethereum_usdt"
],
"priority": 2
}
```
Here,
- `intermediaries` are third-party entities (like exchanges or managed balances) used when direct transfers aren’t possible—especially across networks. For example, to move USDT from Tron to Ethereum, an intermediary like Kraken can receive the funds on one chain and issue them on another. They can also be used on the same network for routing, compliance, or control.
- `priority` is a numerical ranking of eligible sources (1 is highest). If multiple sources are eligible, the one with the lowest priority number is used first.
## Trigger a Manual Top-Up
If needed, you can manually trigger a balance adjustment by sending a [`POST /treasury/v1/balance-adjustments`](/l1-api/treasury/execute-balance-adjustment-1) request with the following body where you specify the desired `amount`:
```json title="Example request"
{
"requestId": "0197170c-b71d-70bc-afbe-5dd925c8620d",
"managedBalanceId": "bvnk_tron_usdt",
"amount": 100
}
```
## View automated transactions
To view transactions initiated by smart treasury operations, send a `GET /treasury/v1/treasury-operations` request specifying the page number and page size as required.
In the response, you receive the complete list of all operations and their status.
See the following guides for more information on each section:
- [Manage balances](/layer1/use-cases/treasury/automate-treasury-top-ups/manage-balances)
- [Manage funding sources](/layer1/use-cases/treasury/automate-treasury-top-ups/manage-funding-sources)
- [View and adjust balance](/layer1/use-cases/treasury/automate-treasury-top-ups/view-and-adjust-balance)
- [Predict top-ups](/layer1/use-cases/treasury/automate-treasury-top-ups/predict-top-ups)
- [Receive notifications](/layer1/use-cases/treasury/automate-treasury-top-ups/receive-notification)
---
## Receive notifications
When Layer1 detects a managed balance transaction , the following webhooks types are sent out:
- `layer1:treasury:notification` sends updates on Treasury events to Slack.
- `layer1:treasury:notice` sends updates to custom webpages.
You can receive the following notifications:
- :information_source: Executing operation(s): TRANSFER `{managedBalanceId1}`->`{managedBalanceId2}` `{amount}`.
- :information_source: `{managedBalanceId}` balance adjusted by `{amount}`.
- :information_source: Treasury transfer(s) `{transactionId}` adjusting `{managedBalanceId}` by `{amount}` auto-approved.
- :warning: Treasury transfer(s) `{transactionId}` adjusting `{managedBalanceId}` by `{amount}` awaiting approval at [https://app.layer1.bvnk.com/approvals](https://app.layer1.bvnk.com/approvals).
- :warning: Adjustment of `{amount}` for `{managedBalanceId1}` rejected.
- :warning: Current balance for `{managedBalanceId1}` is `{value}`, which is below the warning threshold of `{WARNING_value}`.
The payload always features the same fields. The only distinction is the `Source` field, which can be one of the following:
- `CONFIGURATION`: Parameters of a Managed Balance are configured via [API](/layer1/use-cases/treasury/automate-treasury-top-ups/manage-balances#create-a-managed-balance).
- `FORECAST`: Parameters of a Managed Balance are dynamically adjusted by the [prediction feature](/layer1/use-cases/treasury/automate-treasury-top-ups/predict-top-ups).
```json title="Example Payload"
{
"type": "INFO",
"eventType": "TREASURY_OPERATIONS_QUEUED",
"entityCode": "ethereum",
"asset": "usdt",
"balanceAtEventTimestamp": 1500.50,
"message": "Treasury operations queued",
"context": {
"operations": [
{
"amount": 2500.00,
"operationId": "01972084-cd4e-7800-bfb3-8c98eb355e8b",
"source": "bvnk_binance_usdt",
"type": "TRANSFER",
"target": "bvnk_kraken_usdt"
},
{
"amount": 1000.00,
"operationId": "01972084-cd4e-7dbe-bfb5-fd0c6428b38a",
"source": "bvnk_coinbase_usdt",
"type": "TRANSFER",
"target": "bvnk_kraken_usdt"
}
]
}
}
```
```json title="Example Payload"
{
"type": "INFO",
"eventType": "BALANCE_ADJUSTMENT_SUCCEEDED",
"entityCode": "ethereum",
"asset": "usdt",
"balanceAtEventTimestamp": 1500.50,
"message": "Balance adjustment succeeded",
"context": {
"amount": 3500.00,
"thresholds": [
{
"type": "MIN",
"value": 1000.00,
"source": "CONFIGURATION"
},
{
"type": "TARGET",
"value": 5000.00,
"source": "CONFIGURATION"
}
]
}
}
```
```json title="Example Payload"
{
"type": "WARNING",
"eventType": "TREASURY_OPERATION_TIMED_OUT",
"entityCode": "ethereum",
"asset": "usdt",
"balanceAtEventTimestamp": 1500.50,
"message": "Treasury operation timed out",
"context": {
"operationId": "01972084-cd50-7395-aab2-58a1c261cf20",
"amount": 3000.00,
"source": "bvnk_binance_usdt",
"type": "TRANSFER",
"target": "bvnk_kraken_usdt"
}
}
```
```json title="Example Payload"
{
"type": "ERROR",
"eventType": "TREASURY_OPERATION_FAILED",
"entityCode": "ethereum",
"asset": "usdt",
"balanceAtEventTimestamp": 1500.50,
"message": "Treasury operation failed",
"context": {
"operationId": "01972084-cd50-7400-aab5-9f2dd3857ca2",
"amount": 3000.00,
"source": "bvnk_binance_usdt",
"type": "TRANSFER",
"target": "bvnk_kraken_usdt"
}
}
```
```json title="Example Payload"
{
"type": "INFO",
"eventType": "APPROVAL_REQUESTED",
"entityCode": "ethereum",
"asset": "usdt",
"balanceAtEventTimestamp": 1500.50,
"message": "Approval requested",
"context": {
"sources": [
{
"managedBalanceId": "bvnk_binance_usdt",
"amount": 3000.00,
"asset": "usdt"
},
{
"managedBalanceId": "bvnk_tron_usdt",
"amount": 3000.00,
"asset": "usdt"
}
],
"thresholds": [
{
"type": "MIN",
"value": 1000.00,
"source": "CONFIGURATION"
},
{
"type": "TARGET",
"value": 5000.00,
"source": "CONFIGURATION"
}
],
"amount": 3500.00
}
}
```
```json title="Example Payload"
{
"type": "INFO",
"eventType": "AUTO_APPROVAL_GRANTED",
"entityCode": "ethereum",
"asset": "usdt",
"balanceAtEventTimestamp": 1500.50,
"message": "Auto approval granted",
"context": {
"sources": [
{
"managedBalanceId": "bvnk_binance_usdt",
"amount": 3000.00,
"asset": "usdt"
},
{
"managedBalanceId": "bvnk_tron_usdt",
"amount": 3000.00,
"asset": "usdt"
}
],
"thresholds": [
{
"type": "MIN",
"value": 1000.00,
"source": "CONFIGURATION"
},
{
"type": "TARGET",
"value": 5000.00,
"source": "CONFIGURATION"
}
],
"amount": 3500.00
}
}
```
```json title="Example Payload"
{
"type": "WARNING",
"eventType": "LOW_BALANCE_DETECTED",
"entityCode": "ethereum",
"asset": "usdt",
"balanceAtEventTimestamp": 1500.50,
"message": "Low balance detected",
"context": {
"thresholds": [
{
"type": "WARNING",
"value": 2000.00,
"source": "CONFIGURATION"
},
{
"type": "MIN",
"value": 1000.00,
"source": "CONFIGURATION"
},
{
"type": "TARGET",
"value": 5000.00,
"source": "CONFIGURATION"
}
]
}
}
```
```json title="Example Payload"
{
"type": "WARNING",
"eventType": "NO_ELIGIBLE_SOURCES_FOUND",
"entityCode": "ethereum",
"asset": "usdt",
"balanceAtEventTimestamp": 1500.50,
"message": "No eligible sources found",
"context": {
"thresholds": [
{
"type": "MIN",
"value": 1000.00,
"source": "CONFIGURATION"
},
{
"type": "TARGET",
"value": 5000.00,
"source": "CONFIGURATION"
}
],
"amount": 3500.00
}
}
```
```json title="Example Payload"
{
"type": "WARNING",
"eventType": "APPROVAL_REQUEST_REJECTED",
"entityCode": "ethereum",
"asset": "usdt",
"balanceAtEventTimestamp": 1500.50,
"message": "Approval request rejected",
"context": {
"amount": 3500.00,
"thresholds": [
{
"type": "MIN",
"value": 1000.00,
"source": "CONFIGURATION"
},
{
"type": "TARGET",
"value": 5000.00,
"source": "CONFIGURATION"
}
]
}
}
```
---
## View and adjust balance
## Trigger a manual balance adjustment (top‑up)
To **create a manual balance adjustment**, send a [`POST /treasury/v1/balance-adjustments`](/l1-api/treasury/execute-balance-adjustment-1) request with JSON body:
```json title="Example Request"
{
"requestId": "12345678-1234-1234-1234-123456789012",
"managedBalanceId": "ag_layer1_usdt",
"amount": 20000
}
```
***
## List treasury operations
To **list treasury operations**, send a [`GET /treasury/v1/treasury-operations`](/l1-api/treasury/get-treasury-operations) request with parameters:
In the response, you receive the complete list of all operation and their status.
```json title="Example Response"
{
"content": [
{
"eventId": "0197170c-b71d-70bc-afbe-5dd925c8620d",
"id": "0197170c-b71d-70bc-afbe-5dd925c8fa12",
"operationIndex": 0,
"source": "bvnk_tron_usdt",
"target": "bvnk_ethereum_usdt",
"amount": 100,
"externalTrackingId": "ext-track-123",
"externalOperationType": "LAYER1_VENUE_WITHDRAWAL",
"status": "CREATED",
"createdAt": "2025-06-06T13:46:58.425Z"
}
],
"totalElements": 1,
"totalPages": 1,
"pageNumber": 0,
"pageSize": 50
}
```
---
## Automate treasury top-ups
The Smart Treasury feature automates liquidity top-ups based on configurable thresholds and AI-powered forecasts derived from your treasury history. This helps ensure your Layer1 Wallet always maintains sufficient balance to support trading and withdrawals—even during periods of volatility or high activity.
This guide will walk you through how to integrate with the Layer1 Treasury API to:
- Monitor and manage your wallet balances
- Set up automatic top-ups using pre-defined rules
- Leverage forecasting to anticipate and prevent liquidity shortfalls

## Use Cases: When and How Smart Treasury Triggers Top-Ups
The Smart Treasury system helps ensure your Layer1 Wallet always has sufficient liquidity by monitoring multiple inputs and automatically triggering top-ups when needed. It supports the following use cases, which can be enabled individually or in combination:
1. **Minimum Threshold Enforcement**
Automatically top up assets that fall below a configured minimum balance. This rule ensures you always maintain a baseline level of liquidity to meet expected trading and withdrawal demand.
Example: You set a 50 USDC minimum on a specific wallet. If the balance drops to 50, the system initiates a top-up.
2. **ML-Powered Forecasting**
The treasury system uses historical transaction data to forecast future needs and proactively top up balances before they drop. This is especially useful during periods of high volatility or when preparing for known events (for example, campaigns or market shifts).
Example: Based on past weekly activity, the system predicts a spike in ETH withdrawals and initiates a top-up ahead of time.
3. **Incoming Withdrawal Coverage**
If a new withdrawal request exceeds the available balance, the system will automatically move the transaction to FUNDS_NEEDED and trigger a top-up to fulfill it—eliminating the need for manual intervention.
Example: A user requests a 10 BTC withdrawal, but your wallet only has 8 BTC. The system detects the shortfall and initiates a top-up to fulfil the withdrawal (+2 BTC) and restore the post-withdrawal balance to the target balance (+8 BTC).
4. **Gas Fee Management**
The system can monitor wallets you use to fund gas fees (for example, ETH for ERC-20 tokens or MATIC for Polygon) and trigger top-ups to ensure sufficient funds for transaction fees.
Example: Your operational wallet runs low on ETH needed to process ERC-20 withdrawals. The system identifies the deficit and triggers a gas fee top-up.
These use cases work together seamlessly and combing them gives you the most resilient liquidity strategy.
## Prerequisites
Before integrating Smart Treasury features, ensure your API key has the `treasury:admin` role.
To check your roles and permissions, in Layer1 Portal, go to **Manage account** > **Team members**, open the menu (**⋮**) next to your user, and click **Edit**. Your roles are listed under **Assign roles to user**.
## Key concepts
| Term | Description |
| ---------------------- | ---------------------------------------------------------------------------------------------- |
| **Managed Balance** | A wallet that represents a defined source of funds within the Smart Treasury system. Once created, it can be used in the following ways:Auto top-up source: mark the balance to be monitored by the system, so auto top-ups are initiated when thresholds are reached.Liquidity source: provide funds directly for top-ups.Conversion routing: use as an intermediary source of liquidity to facilitate conversions. |
| **Thresholds** | Rules that trigger notifications or top-ups (min, warning, target, buffer) |
| **Eligible Source** | A liquidity provider (for example Kraken, Binance) that can fund top-ups or another asset pool |
| **Balance Adjustment** | A manual or automated top-up |
| **Buffer Factor** | Safety margin preventing cascading liquidity issues |
| **Forecasting** | Predictive system based on historical data to maintain sufficient balance |
## Related API endpoints
- [Create managed balance](/l1-api/treasury/create-managed-balance)
- [Upsert managed balance](/l1-api/treasury/upsert-managed-balance)
- [Patch thresholds](/l1-api/treasury/patch-thresholds)
- [Upsert eligible source](/l1-api/treasury/upsert-eligible-source)
- [Execute balance adjustment](/l1-api/treasury/execute-balance-adjustment)
- [Get treasury operations](/l1-api/treasury/get-treasury-operations)
---
## Smart treasury
---
## Dust classification
To reduce unnecessary screening and save on costs, Layer1 applies a **dust classification filter** before forwarding transactions. This filter uses each asset's precision to identify and discard negligible "dust" amounts — typically the result of dust attacks — that don't represent meaningful activity or actual customer activity.
The logic works as follows:
1. Each asset has a known `decimal precision`, which determines how many digits can appear after the decimal point.
2. To detect dust, we look at all digits up to the (`decimal_precision` minus 1)th decimal place.
3. If all of those digits are zero the amount is classified as dust.
4. Otherwise, the amount is considered legitimate and proceeds to our transaction screening provider.
### Example: Solana (SOL)
- `decimal precision`= 9
- We inspect the first 8 digits after the decimal point.
- If a transaction amount is 0.000000001, the first 8 digits are all zero → this is dust.
- If a transaction amount is 0.000000010, the 8th digit is non-zero → this is not dust.
This logic is applied automatically across all assets based on the precision values shown in the table below.
| Network | Asset | Decimal Precision |
| ------------ | :-----: | :-----------------: |
| ETHEREUM | ETH | 18 |
| ETHEREUM | USDT | 6 |
| ETHEREUM | USDC | 6 |
| ETHEREUM | DAI | 18 |
| BINANCE | ETH | 18 |
| TRON | TRX | 6 |
| TRON | USDT | 6 |
| RIPPLE | XRP | 6 |
| BINANCE | BNB | 18 |
| BINANCE | USDT | 18 |
| BINANCE | USDC | 18 |
| BINANCE | DAI | 18 |
| ETHEREUM | PYUSD | 6 |
| ETHEREUM | FDUSD | 18 |
| BINANCE | FDUSD | 18 |
| SOLANA | SOL | 9 |
| SOLANA | USDC | 6 |
| ETHEREUM | WLD | 18 |
| POLYGON | POL | 18 |
| POLYGON | USDC | 6 |
| POLYGON | USDT | 6 |
| ETHEREUM | SHIB | 18 |
| ETHEREUM | LINK | 18 |
| BITCOIN_CASH | BCH | 8 |
| BITCOIN | BTC | 8 |
| LITECOIN | LTC | 8 |
| DOGECOIN | DOGE | 8 |
| BINANCE | SHIB | 18 |
| BINANCE | LINK | 18 |
| SOLANA | USDT | 6 |
| SOLANA | PYUSD | 6 |
| BASE | ETH | 18 |
| BASE | USDC | 6 |
| SOLANA | USDG | 6 |
---
## Reporting and reconciliation
Layer1 reporting tools help you track wallet activity, monitor balances, and export data to match against your internal financial records. Use this guide to create exports and understand reconciliation best practices.
## Exporting reports
Layer1 supports exporting two main types of reports:
1. Transaction report: A detailed list of transactions for a specific time period, filtered by asset pool, asset, transaction type etc.
2. Balance report: A current snapshot of your balances per asset
### Export a transaction report
To export a transaction report:
1. Go to **Digital Assets** > **Exports**.
2. Select **Export**.
3. **Apply filters** to create an export that meets your needs:
1. Asset pool (for example, Revenue Wallet, Treasury Wallet)
2. Date range (for example, "1st-30th June", or choose a custom range)
3. Date type filter (for example, Created at or Updated at date)
4. Network (for example, Bitcoin, Ethereum)
5. Asset (for example, USDT, Ethereum)
6. Status (for example, Success, Failed)
7. Type (for example, Deposit, Withdrawal)
4. Click **Generate Export**.
5. Wait for the report to finish generating. Once ready, it will appear on the Exports page. You may need to refresh the page to see the latest status or download link.
### Export a balance report
To export a balance report:
1. Go to **Digital Assets** > **Exports**.
2. Select **Export**.
3. **Apply filters** to create an export that meets your needs:
1. Asset pool (for example, Revenue Wallet, Treasury Wallet)
2. Network (for example, Bitcoin, Ethereum)
3. Asset (for example, USDT, Ethereum)
4. Click **Generate Export**.
5. Wait for the report to finish generating. Once ready, it will appear on the Exports page. You may need to refresh the page to see the latest status or download link.
## Reconciliation notes
When reconciling your platform exports with internal records, keep the following in mind:
### Timestamp alignment
- All timestamps are in UTC.
- Ensure your internal ledger timestamps match for accurate reconciliation.
### Transaction amount and network fee logic
The 'amount' shown in the transaction report reflects the value debited or credited during a transaction. However, the inclusion of network fees in this amount depends on the type of transaction and whether the transaction currency is the same as the network fee (gas) currency.
:::warning
The "amount" field does not consistently include network fees across all transaction types.
For now, the platform does not support a unified "total spent" or "net received" column, as this calculation varies by context. The logic below outlines how to interpret the amount shown in exports.
:::
| Transaction Type | Tx & Gas Currency Relationship | Example | Logic Note |
| ----------------- | ------------------------------ | ---------------- | ------------------------------------------------------- |
| **Withdrawal** | Same currency | ETH on Ethereum | Amount includes network fee (`tx amount + network fee`) |
| **Withdrawal** | Different currency | USDT on Ethereum | Amount does not include network fee (`tx amount only`) |
| **Deposit** | Same currency | ETH on Ethereum | Amount does not include network fee (`tx amount only`) |
| **Deposit** | Different currency | USDT on Ethereum | Amount does not include network fee (`tx amount only`) |
| **Consolidation** | Same currency | ETH on Ethereum | Amount includes network fee (`tx amount + network fee`) |
| **Consolidation** | Different currency | USDT on Ethereum | Amount does not include network fee (`tx amount only`) |
---
## Roles and permissions
Layer1 uses a role-based access control system to manage permissions for users and API keys. Roles define the scope of actions available to an identity and are grouped into two scopes:
- **Tenant Roles**: Apply at the platform (Layer1 tenant) level
- **Account Roles**: Apply at the merchant account level
Each role grants access to specific capabilities within the platform.
:::note
You can only assign roles to others that you hold yourself.
:::
## Recommendations
- Assign roles based on the principle of least privilege: grant only the minimum permissions necessary to perform the tasks, reducing the attack surface and limiting potential misuse.
- Combine roles where needed for hybrid access. For example, `viewer` + `monitor`.
## Tenant roles
**Tenant** is the direct customer of Layer1. Tenant roles define access at the Layer1 platform (or tenant) level. These roles are designed for customers who utilise Layer1 to manage their internal treasury operations, encompassing infrastructure configuration, transaction workflows, and digital asset lifecycle management.
They are typically assigned to treasury, operations, and engineering teams who need to interact with wallets, networks, liquidity, and exchange integrations across the organisation.
Use these roles to delegate specific responsibilities while maintaining firm control over your treasury environment.
### User management
| Role | Description | Use case |
| :-------------- | :-------------------- | :------------------------------------------------------------------------- |
| `user:operator` | Create and edit users | Assign to administrators managing user access and permissions |
| `user:viewer` | View existing users | Assign to stakeholders monitoring user access without modification rights |
### Digital assets
| Role | Description | Use case |
| :-------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------- |
| `crypto:admin` | Manage asset pools and addressesConsolidate assets into master addresses | Assign to users who manage digital asset infrastructure and oversee asset consolidation processes |
| `crypto:operator` | Manage networks, asset pools, and addressesCreate and view transactionsClaim stuck deposits | Assign to users who perform day-to-day blockchain operations and manage transactions |
| `crypto:monitor` | View networks, asset pools, addresses, and transactionsClaim stuck depositsApprove or reject screened transactions | Assign to compliance officers or monitoring roles who oversee transaction activities |
| `crypto:viewer` | View networks, asset pools, addresses, and transactionsClaim stuck depositsView screened transactionsInitiate and view exports | Assign to auditors or stakeholders requiring comprehensive visibility without modification rights |
| `crypto:customer:support` | View networks, asset pools, addresses, and transactionsClaim stuck depositsView screened transactions | Assign to customer support teams assisting users with transaction-related inquiries |
| `crypto:config:operator` | Manage digital asset node configuration. Relevant only for on-premise deployment model | Assign to technical staff responsible for configuring on-premise digital asset nodes |
| `crypto:config:viewer` | View digital asset node configuration. Relevant only for on-premise deployment model | Assign to auditors or stakeholders without edit permissions who monitor node configurations |
| `crypto:liquidity:operator` | Consolidate assets into master addressesClaim stuck deposits | Assign to users managing liquidity operations and asset consolidation processes |
### Trading
| Role | Description| Use case |
| -------------------------- | ---------- | ----------------------------- |
| `trade:liquidity:operator` | Create and view deposit addresses at trading venuesView venue balancesCreate and view venue transfers | Assign to users who manage liquidity across trading venues |
| `trade:liquidity:viewer` | View deposit addresses at trading venuesView venue balancesView venue transfersView exchange rates | Assign to stakeholders who monitor trading venue activities and exchange rates |
| `trade:trading:operator` | View venue balancesExecute conversionsView conversionsEnable/Disable trading symbols | Assign to traders who execute conversions, monitor trading activities and enable trading pairs for exchange venues |
| `trade:trading:viewer` | View venue balancesView exchange ratesView conversions | Assign to analysts or stakeholders to monitor trading activities without execution rights |
| `trade:config:operator` | Configure and view trade venues | Assign to users responsible for configuring trading parameters |
| `trade:config:viewer` | View trade venues | Assign to auditors or stakeholders to monitor trade configurations without edit permissions |
### Compliance
| Role | Description | Use case |
| :-------------------- | :--------------------------- | :------------------------------------------------------------------------------------------------------------- |
| `compliance:admin` | Configure screening settings | Assign to compliance officers responsible for configuring screening settings, dust thresholds and travel rule |
| `compliance:operator` | Bypass screening | Assign to compliance officers with the permission to bypass screening for individual transactions |
### Approvals
| Role | Description | Use case |
| ---------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `approval-settings:operator` | Configure approvals settings | Assign to trusted operations or administrator leads responsible for configuring transaction security settings |
| `crypto:withdrawal:approver` | Approve or reject withdrawals | Assign to trusted leads or treasury admins responsible for reviewing withdrawals requiring review by your security settings |
| `contact:address:approver` | Approve or reject new addresses | Assign to trusted leads responsible for reviewing new trusted addresses added to a contact requiring review by your security settings |
### Treasury
| Role | Description | Use case |
| :--------------- | :------------------------------------ | :------------------------------------------------------------------------------------------- |
| `treasury:admin` | Create and configure managed balances | Assign to users responsible for configuring thresholds for managed balances (Smart Treasury) |
### Clients (API Keys)
| Role | Description | Use case |
| :---------------- | :-------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------- |
| `client:operator` | Create, edit, and delete API keys | Assign to developers or integration engineers who manage API keys for various services or applications |
| `client:viewer` | View API keys | Assign to stakeholders who need visibility into API key usage without modification rights |
| `secret:operator` | Connect 3rd party service | Assign to developers or integration engineers who manage connecting 3rd party services (exchanges/ screening providers) |
| `secret:viewer` | View connected 3rd party services | Assign to stakeholders who need visibility into connected 3rd party services (exchanges/ screening providers) without modification rights |
### Hooks
| Role | Description | Use case |
| --- | --- | --- |
| `hook:operator` | Create and view hook destinationsView hook event typesReplay hooks for given transaction ID | Assign to developers or administrators responsible for setting up and managing webhook destinations |
| `hook:viewer` | View hook destinationsView hook event types | Assign to users or teams to provide visibility into webhook destination setup without granting editing permissions |
| `hook:config:operator` | View and edit hook configuration. Relevant only for on-premise deployment model | Assign to developers or administrators responsible for setting up and managing webhook configurations |
| `hook:config:viewer` | View hook configuration. Relevant only for on-premise deployment model | Assign to users or teams to provide visibility into webhook configuration setup without editing permissions |
## Managing merchants
If you leverage our white-label Merchant Payment Engine, the following roles apply to managing your merchants, which are referred to as “accounts” in the platform. These roles allow your tenant users to manage and oversee merchant setup, permissions, and charged fees.
### Accounts
| Role | Description | Use case |
| :--------------------------- | :-------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- |
| `platform:operator` | Create, edit, and view your accounts | Assign to trusted operations leads responsible for creating and maintaining merchant accounts |
| `platform:viewer` | View your accounts | Assign to operations users to provide visibility into merchant accounts without granting modification rights |
| `merchant:settings:operator` | View your merchant balancesEdit your merchant wallet settings | Assign to operations users who need to see your merchant balances and edit the processing settings of your merchant's wallets |
| `ledger:admin` | Adjust your merchant's wallet balances manually | Assign to operations users who need to manually adjust the current balance of merchant's wallets |
### Fees
| Role | Description | Use case |
| --- | --- | --- |
| `fee:admin` | Create, edit, and delete fee typesCreate, edit, and delete account fees | Assign to operations users responsible for setting up and maintaining pricing or fee structures for merchants |
***
## Account roles
If you leverage our **white-label Merchant Payment Engine**, the following roles apply to your **Merchant users** and **API keys**. These roles govern what users can view and manage within the **Account Portal** (which is distinct from the Layer1 Tenant Portal).
Each role is designed to align with specific operational needs, from viewing data to performing full transaction management. These roles help enforce the principle of least privilege, ensuring users only access what they need.
### Users
| Role | Description | Use case |
| --------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------ |
| `user:operator` | Create, edit, and delete users | Assign to team leads who need to manage their team's access and information |
| `user:viewer` | View existing users | Assign to auditors or stakeholders to provide visibility into user data without granting modification rights |
### Clients (API Keys)
| Role | Description | Use case |
| ----------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `client:operator` | Create, edit, and delete API keys | Assign to developers or integration engineers who manage API keys for various services or applications |
| `client:viewer` | View API keys | Assign to stakeholders to provide visibility into API key usage without granting modification rights |
### Wallets
| Role | Description | Use case |
| ----------------- | -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `ledger:operator` | Create and view account level wallets—your merchant's balances | Assign to users who handle day-to-day payments and manage transactions |
| `ledger:viewer` | View account level wallets—your merchant's balances | Assign to auditors or stakeholders to provide visibility into wallets without granting modification rights |
### Conversions
| Role | Description | Use case |
| --------------------- | ------------------------------- | ------------------------------------------------------------- |
| `conversion:operator` | Convert between wallet balances | Assign to users who manage balances across wallets day-to-day |
### Channels
| Role | Description | Use case |
| --- | --- | --- |
| `channels:operator` | Create, edit, soft delete, and view channelsView channel payments | Assign to users who handle day-to-day payments and manage transactions |
| `channels:viewer` | View channelsView channel payments | Assign to auditors or stakeholders to provide visibility without granting modification rights |
### Payments
| Role | Description | Use case |
| ------------------ | -------------------- | ----------------------------------------------------------------------------------------------------------- |
| `payment:operator` | Create payment links | Assign to users who handle day-to-day payments and manage transactions |
| `payment:viewer` | View payment | Assign to auditors or stakeholders to provide visibility into payments without granting modification rights |
### Approvals
| Role | Description | Use case |
| ---------------------------- | -------------------------- | ------------------------------------------------------------------------ |
| `approval-settings:operator` | Manage approval rules | Manage your approval rules that should be applied to payments |
| `payment:approver` | Approve or reject payments | Assign to trusted leads responsible for reviewing and approving payments |
### Hooks
| Role | Description | Use case |
| --------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `hook:operator` | Create and view hook destinationsView hook event types | Assign to developers or administrators responsible for setting up and managing webhook destinations |
| `hook:viewer` | View hook destinationsView hook event types | Assign to users or teams to provide visibility into webhook destination setup without granting editing permissions |
---
## Supported assets and networks
The supported digital assets on our platform, including details such as their code, protocols, and the required block confirmations for transactions.
### Production assets
| Asset Name | Asset Code | Network | Block Confirmations Required |
| ----------------- | ---------- | -------- | --------------------------------- |
| Binance Coin | **BNB** | BSC | 4 |
| Bitcoin | **BTC** | | 1 |
| Bitcoin Cash | **BCH** | | 1 |
| Chainlink | **LINK** | Ethereum | 4 |
| | **LINK** | BSC | 4 |
| Dai | **DAI** | Ethereum | 4 |
| | **DAI** | BSC | 4 |
| Dogecoin | **DOGE** | | 4 |
| Ethereum | **ETH** | Ethereum | 4 |
| Ethereum | **ETH** | BASE | 10 |
| Ethereum | **ETH** | BSC | 4 |
| Ethereum | **ETH** | Arbitrum | 64 |
| Eurocoin | **EURC** | Ethereum | 12 |
| First Digital USD | **FDUSD** | Ethereum | 4 |
| | **FDUSD** | BSC | 4 |
| Global Dollar | **USDG** | Solana | 32 |
| Litecoin | **LTC** | | 4 |
| PayPal USD | **PYUSD** | Ethereum | 4 |
| | **PYUSD** | Solana | 32 |
| Polygon | **POL** | | 4 |
| Ripple | **XRP** | | When included in validated ledger |
| Shiba Inu | **SHIB** | Ethereum | 4 |
| | **SHIB** | BSC | 4 |
| Solana | **SOL** | | 32 |
| Tether | **USDT** | Ethereum | 4 |
| | **USDT** | Tron | 4 |
| | **USDT** | Solana | 32 |
| | **USDT** | Polygon | 4 |
| | **USDT** | BSC | 4 |
| Tronix | **TRX** | | 4 |
| USD Coin | **USDC** | Arbitrum | 300 |
| | **USDC** | Arc | 1 |
| | **USDC** | BASE | 10 |
| | **USDC** | BSC | 4 |
| | **USDC** | Ethereum | 4 |
| | **USDC** | Polygon | 4 |
| | **USDC** | Solana | 32 |
| Worldcoin | **WLD** | Ethereum | 4 |
### Staking assets
| Asset Name | Asset Code | Network | Description |
| --- | --- | --- | --- |
| Staked Tronix | **S-TRX** | Tron | A staking transaction or the staked balance of TRX |
| Unstaked Tronix | **U-TRX** | Tron | An unstaking transaction or the unstaked balance of TRX currently in 14-day lock up period |
| Delegated Tronix | **D-TRX** | Tron | A delegation or undelegation of staked TRX from the master address to a different Tron wallet or the delegated balance of TRX. Used during the consolidation workflow |
### Sandbox testing assets
| Asset Name | Asset Code | Network | Test Network |
| ------------ | ---------- | -------- | --------------- |
| USD Coin | **USDC** | Ethereum | Sepolia Testnet |
| | **USDC** | Polygon | Amoy Testnet |
| | **USDC** | Solana | Solana Devnet |
| | **USDC** | BSC | BNB Testnet |
| Tether | **USDT** | Ethereum | Sepolia Testnet |
| | **USDT** | Tron | Nile Testnet |
| | **USDT** | Solana | Solana Devnet |
| | **USDT** | Polygon | Amoy Testnet |
| | **USDT** | BSC | BNB Testnet |
| Ethereum | **ETH** | | Sepolia Testnet |
| Solana | **SOL** | | Solana Devnet |
| Tronix | **TRX** | | Nile Testnet |
| Ripple | **XRP** | | Ripple Testnet |
| Polygon | **POL** | | Amoy Testnet |
| Binance Coin | **BNB** | BSC | BNB Testnet |
### Display assets
The display/base currency for your account is set during account configuration. USD, EUR and GBP are supported as display currencies.
---
## Authentication
HTTP Message Signatures let you cryptographically prove both _what_ you sent and _when_ you sent it. Instead of relying solely on TLS (which protects the transport channel), message signatures protect the _message_ itself end-to-end:
1. **Authenticity**: The receiver knows the request came from a holder of your private key.
2. **Integrity**: Any tampering with the method, URL, or body will break the signature.
3. **Replay Protection**: Including a timestamp prevents attackers from resubmitting old requests.
By embedding the signature into standard HTTP headers, you keep your API calls fully HTTP-compliant while adding a robust cryptographic layer.
## Generate RSA keys for HTTP signatures
To use HTTP signatures in your requests, you need an RSA key pair with:
- **Private key**. Use it to sign your requests. It must be stored securely in your system.
- **Public key**. Register it with Layer1 to authenticate your requests. The public key will be used to register your client or share it during setup.
To create an RSA key pair:
1. Run the following command to generate a private key:
```java title="Generate private key"
openssl genrsa -out api-client-key.pem
```
2. Extract the public key from the private key:
```java title="Extract public key"
openssl rsa -in api-client-key.pem -pubout -out api-client-public-key.pem
```
:::tip[Best practice]
- Store private keys in secure hardware or vault solutions where only your application can access it.
- Rotate keys periodically (for example, every 90 days) and automate rollover in your signer code.
:::
## Option 1: Add a new `API Key` to your tenant
If you already have a `API Key` attached to your account and associated with the public certificate, skip this and go to the next step.
To add an `API Key` to your tenant, do the following:
1. Log in to your Layer1 portal.
2. Go to **Integrations** > **API Keys**, locate the required client, and click **+ Client**.
3. Complete the following fields:
- **Name**: Enter the name for this record.
- **Network whitelist**: Enter the IP addresses for which the access must be provided and click **Add**.
To provide access to everyone, enter `0.0.0.0`.
- **Public key**: Paste your [generated public key](/l1-api/api-overview/authentication#generate-rsa-keys-for-http-signatures).
- **Assign roles to user**: Specify the required roles and permissions for different operations.
4. Click **Add**. The new client appears in the list.
## Option 2: Configure your existing API Key
The [above private key](/l1-api/api-overview/authentication#generate-rsa-keys-for-http-signatures) remains securely with your application, while the public key must be registered in Layer1 to authenticate your requests.
If you have already added the public key, IP addresses, and roles to your tenant, you can skip this step and go to [Get your clientId](/l1-api/api-overview/authentication#get-your-clientid).
After extracting your public RSA key, add it to your account along with the IP addresses you require to be whitelisted:
1. On the Layer1 Portal, go to **Integrations** > **API Keys**, locate the required client, and click the **Edit** icon.
2. In the **Public Key** box, paste your [generated public key](/l1-api/api-overview/authentication#generate-rsa-keys-for-http-signatures).
3. In the **Network whitelist** box, enter the IP addresses for which the access must be provided and click **Add**.
To provide access to everyone, enter `0.0.0.0`.
4. In the **Assign roles to user**, specify the required roles.
5. Click **Update** to save the changes.
## Get your `clientId`
The `clientId` parameter within the signature headers is crucial for identifying the key used to sign the request. When you generate the signature headers, the `clientId` value is set to match the public key registered with Layer1.
To get your `clientId`,
1. In your Layer1 portal, go to **Integrations** > **API Keys** and find the client to which you've just added the public key.
2. Click the icon to copy the required line.
:::danger
If you can't access the **API Keys** section, request your `clientId` from your Solutions manager and ask them to add your public key and allowed IP addresses for your account.
:::
## Sign HTTP requests
Layer1 requires request signing based on the [HTTP Message Signatures standard (RFC 9421)](https://www.rfc-editor.org/rfc/rfc9421.html). Each signature is a combination of the following HTTP components:
- HTTP method (`@method`)
- Request URL (`@target-uri`)
- Request body (`content-digest`). The SHA-256 digest of the request body ensures that the data has not been tampered with
- Timestamp (`date`). Date header is also included to mitigate replay attacks by ensuring the request is recent.
The signature is constructed by:
1. Selecting HTTP components (`@method`, `@target-uri`, `content-digest`, `date`).
2. Creating a base string.
3. Signing the string using your private key.
```java title="Example Signed Request Headers"
POST /api/transaction HTTP/1.1
Host: api.layer1.com
Date: Tue, 27 May 2025 10:21:54 GMT
Content-Type: application/json
Content-Digest: sha-256=:W6ph5Mm5Pz8GgiULbPgzG37mj9g=:
Signature-Input: sig=("@method" "@target-uri" "content-digest" "date");created=1716792114;keyid="client-123";alg="rsa-v1_5-sha256"
Signature: sig=:MEUCIQDn...==
```
You can now construct a request body with headers using your `clientId` and sign it with the generated public key. The following code block is an example of the class to sign your requests.
```java title="Java Example Code"
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Map;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.HashMap;
import java.util.Objects;
import java.util.Locale;
public class SignedRequestExample {
private static final String SIGNATURE_ALGORITHM = "rsa-v1_5-sha256";
private static final String DIGEST_ALGORITHM = "sha-256";
private static final DateTimeFormatter HTTP_DATE_FORMATTER = DateTimeFormatter
.ofPattern("EEE, dd MMM yyyy HH:mm:ss 'GMT'")
.withZone(ZoneOffset.UTC)
.withLocale(Locale.US);
public static void main(String[] args) throws IOException, InterruptedException {
// Example input
String base64PrivateKey = """
-----BEGIN PRIVATE KEY-----
MIIEv...your key here...
-----END PRIVATE KEY-----
"""; // Replace with your actual key (can be from env, file, etc.)
String clientId = "YOUR_CLIENT_ID_HERE";
String method = "POST";
String url = "https://layer1.readme.io/openapi/663b2cf0eb5c5c004293d196"; // YOUR LAYER1 API URL
// JSON payload for creating a payment.
String payload = """
{
"name": "Payment Processing Spanish VASP",
"reference": "myUniqueRef333"
}
""";
HttpSigner signer = new HttpSigner(base64PrivateKey, clientId);
Map headers = signer.buildHeaders(url, payload, method);
// Create the HTTP request
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/json")
.method(method, HttpRequest.BodyPublishers.ofString(payload));
// Add signature headers
headers.forEach(requestBuilder::header);
HttpClient client = HttpClient.newHttpClient();
HttpResponse response = client.send(
requestBuilder.build(),
HttpResponse.BodyHandlers.ofString()
);
// Output the response
System.out.println("Status: " + response.statusCode());
System.out.println("Body:\n" + response.body());
}
/**
* Signs HTTP requests according to RFC 9421 using SHA256withRSA.
*/
public static class HttpSigner {
private static final String SIGNATURE_ALGORITHM = "rsa-v1_5-sha256";
private static final String DIGEST_ALGORITHM = "sha-256";
private final PrivateKey privateKey;
private final String clientId;
private static final DateTimeFormatter HTTP_DATE_FORMATTER = DateTimeFormatter
.ofPattern("EEE, dd MMM yyyy HH:mm:ss 'GMT'")
.withZone(ZoneOffset.UTC)
.withLocale(Locale.US);
/**
* Constructor to load the private key from a Base64-encoded string.
*
* @param base64PrivateKey Base64-encoded private key string
*/
public HttpSigner(String base64PrivateKey, String clientId) {
this.clientId = clientId;
this.privateKey = loadPrivateKey(base64PrivateKey);
}
/**
* Load a PrivateKey object from a Base64-encoded string.
*/
private PrivateKey loadPrivateKey(String key) {
try {
String preparedKey = prepareKey(key);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(preparedKey))
);
} catch (Exception e) {
throw new RuntimeException("Failed to load private key", e);
}
}
/**
* This method builds the necessary signature headers and returns them as a map.
*
* @param url The full URL of the request
* @param payload The body of the request (if any, POST, etc)
* @param method The HTTP method of the request
* @return A map containing the signature headers
*/
public Map buildHeaders(String url, String payload, String method) {
Map headerParams = new HashMap<>();
String contentDigest = null;
if (!Objects.isNull(payload) && !payload.isEmpty()) {
try {
contentDigest = createDigest(DIGEST_ALGORITHM, payload);
headerParams.put("Content-Digest", contentDigest);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Failed to create digest", e);
}
}
Instant now = Instant.now();
String dateHeader = HTTP_DATE_FORMATTER.format(now);
headerParams.put("Date", dateHeader);
long createdTimestamp = now.toEpochMilli() / 1000; // Unix timestamp in seconds
String signatureParameters = createSignatureParameters(contentDigest, createdTimestamp);
headerParams.put("Signature-Input", "sig=" + signatureParameters);
try {
String signatureBase = String.format(
"\"@method\": %s%n\"@target-uri\": %s%n%s\"date\": %s%n\"@signature-params\": %s",
method.toUpperCase(),
url,
contentDigest == null ? "" : "\"content-digest\": " + contentDigest + "\n",
dateHeader,
signatureParameters
);
headerParams.put("Signature",
String.format("sig=:%s:",
sign(signatureBase, privateKey)
)
);
} catch (Exception e) {
throw new RuntimeException("Failed to sign request", e);
}
return headerParams;
}
/**
* Remove the header and footer from the private key if generated via openssl, etc.
*
* @param rawKey The raw private key as a string
* @return The prepared key without headers and footers
*/
private String prepareKey(String rawKey) {
String newKey = rawKey.replace("-----BEGIN PRIVATE KEY-----", "");
newKey = newKey.replace("-----END PRIVATE KEY-----", "");
return newKey.replaceAll("\\s+", "");
}
/**
* Assemble the RFC 9421 signature parameters.
*
* @param contentDigest The digest of the content, if applicable
* @param createdTimestamp The Unix timestamp (in seconds) when the signature was created.
* @return The signature parameters as a string
*/
private String createSignatureParameters(String contentDigest, long createdTimestamp) {
StringBuilder components = new StringBuilder("(\"@method\" \"@target-uri\"");
if (contentDigest != null) {
components.append(" \"content-digest\"");
}
components.append(" \"date\"");
return String.format("%s);created=%d;keyid=\"%s\";alg=\"%s\"",
components.toString(),
createdTimestamp,
clientId,
SIGNATURE_ALGORITHM
);
}
/**
* Sign the request using SHA256withRSA.
*
* @param signatureBase The base string to sign
* @param privateKey The private key to use for signing
* @return The Base64-encoded signature
* @throws NoSuchAlgorithmException If the algorithm is not available
* @throws InvalidKeyException If the key is invalid
* @throws SignatureException If the signing process fails
*/
private String sign(String signatureBase, PrivateKey privateKey)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initSign(privateKey);
signer.update(signatureBase.getBytes());
return Base64.getEncoder().encodeToString(signer.sign());
}
/**
* Create and prepare the digest for the content-digest header.
*
* @param digestAlgorithm The algorithm to use for the digest
* @param data The data to digest
* @return The formatted digest string
* @throws NoSuchAlgorithmException If the algorithm is not available
*/
private String createDigest(String digestAlgorithm, String data) throws NoSuchAlgorithmException {
return String.format("%s=:%s:", digestAlgorithm, getDigest(digestAlgorithm, data));
}
/**
* Generate the digest using the specified algorithm.
*
* @param algorithm The algorithm to use
* @param data The data to digest
* @return The Base64-encoded digest
* @throws NoSuchAlgorithmException If the algorithm is not available
*/
private String getDigest(String algorithm, String data) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance(algorithm);
byte[] hash = digest.digest(data.getBytes());
return Base64.getEncoder().encodeToString(hash);
}
}
}
```
## Use signed headers in API requests
Once the headers are generated, they must be attached to your HTTP request. These headers will allow the server to verify the signature using the corresponding public key that you registered Layer1.
```
// Instantiate the http signer
HttpSigner signer = new HttpSigner(privateKey, clientId);
// Generate the signature headers
Map headers = signer.buildHeaders(url, body, method);
```
Here,
- `base64PrivateKey`: Your base64-encoded RSA private key in the **PKCS#8** format.
- `clientId`: Your key identifier registered with Layer1 and visible in the Account Portal.
- `url`: Full API endpoint address you're calling, for example, [https://layer1.readme.io/openapi/663b2cf0eb5c5c004293d196](https://layer1.readme.io/openapi/663b2cf0eb5c5c004293d196).
- `payload`: Request body. Required for POST, PUT, and so on.
- `method`: HTTP method, for example, "POST", "GET".
The headers map returned by `HttpSigner#buildHeaders(...)` includes:
- `Content-Digest`: SHA-256 hash of the payload. Only present for requests with a body.
- `Date`: Current timestamp in the HTTP date format (RFC 7231).
- `Signature-Input`: Metadata describing the signed components (`"@method"`, `"@target-uri"`, etc.).
- `Signature`: The final Base64-encoded digital signature (using SHA256withRSA).
## Troubleshooting
If the signature fails Layer 1 validation, the primary issue may be with the payload and headers.
In this case, verify that you have done the following:
- Correctly acquired and specified your `clientId`.
- Uploaded your **public** key (not private one) to the API key.
- Ensured there are no typos or other errors in the payload.
- Ensured the signed headers are added to the request.
- Ensured that your request was constructed according to our documentation.
---
## Errors
The following reference contains a list of error codes, their messages, and descriptions for errors that may be encountered when using the Layer1 API.
These errors are returned in the following format:
```json
{
"code": "layer1:digital-asset:XXXX",
"status": "",
"message": ""
}
```
## Error Codes
### Validation or Unknown Exception
| Code | Status | Description |
| ------------------------------- | ----------- | ------------------------------------------------- |
| **`layer1:digital-asset:1000`** | BAD_REQUEST | Error given for validation or unknown exceptions. |
### Validation Errors
| Code | Status | Description |
| ------------------------------- | ------------------- | --------------------------------------------------------------- |
| **`layer1:digital-asset:4000`** | BAD_REQUEST | Error given for invalid inputs. |
| **`layer1:digital-asset:4001`** | CONFLICT | Error given when a transfer request exists with different data. |
| **`layer1:digital-asset:4002`** | BAD_REQUEST | Error given for invalid outputs. |
| **`layer1:digital-asset:4003`** | BAD_REQUEST | Error given for an invalid request. |
| **`layer1:digital-asset:4004`** | CONFLICT | Error given when a master key already exists. |
| **`layer1:digital-asset:4005`** | CONFLICT | Error given when there is data inconsistency. |
| **`layer1:digital-asset:4006`** | PRECONDITION_FAILED | Error given when an asset pool is not empty. |
| **`layer1:digital-asset:4007`** | CONFLICT | The error appears when an asset pool is not empty. |
| **`layer1:digital-asset:4008`** | NOT_FOUND | The error appears when the requested item can't be found. |
### Export Job Errors
| Code | Status | Description |
| ------------------------------- | ----------- | ------------------------------------------------------------ |
| **`layer1:digital-asset:4100`** | CONFLICT | Error given when an export job execution is already running. |
| **`layer1:digital-asset:4101`** | CONFLICT | Error given when an export job restart fails. |
| **`layer1:digital-asset:4102`** | BAD_REQUEST | Error given when an export job instance is already complete. |
| **`layer1:digital-asset:4200`** | CONFLICT | The error appears when a consolidation request with this reference already exists for the asset pool. |
| **`layer1:digital-asset:4201`** | BAD_REQUEST | The error appears when the blockchain address is not associated with the specified asset pool. |
| **`layer1:digital-asset:4202`** | BAD_REQUEST | The error appears when the specified address is a master address and cannot be used as a consolidation source. |
## Failure reasons
In webhooks and responses from `GET` endpoints, you can see the `failure.reason` field for describing the reason for an unsuccessful operation. Layer1 supports the reason values, including but not limited to the following:
| Failure reason | Description |
| ----------------------------------------- | -------------------------------------------------------------------------------- |
| **`APPROVAL_REJECTED`** | Transactions was rejected as part of your configured approval policies |
| **`BLOCKCHAIN_TRANSACTION_BUILD_FAILED`** | Could not construct blockchain transaction ahead of signing |
| **`BLOCKCHAIN_TRANSACTION_FAILED`** | Transaction failed on the blockchain |
| **`DUST_AMOUNT`** | Transaction was categorised as a small dust amount |
| **`INSUFFICIENT_FUNDS`** | Not enough funds on balance to process transaction (can also be gas fee balance) |
| **`PROCESSING_FAILED`** | General processing error for a transaction |
| **`SCREENING_REJECTED`** | Transaction was rejected as a result of compliance screening |
---
## Webhooks
Webhooks are critical for real-time notifications within our Layer1 platform, enabling automated updates related to payments, conversions, or new address creation events. When such events occur, Layer1 makes an HTTP request to the URL configured by the user for the webhooks.
## Acknowledging Webhooks
The Layer1 webhooks service expects to receive a 200 HTTP status code as an acknowledgment of a successfully received and validated webhook. This response should be sent immediately upon receipt of the webhook to confirm its delivery and avoid timeout errors.
## Webhook retry policy
If BVNK does not receive a `200` status code, the webhook retry policy will kick in.
The retry policy adds a delay before each retry, starting with a base delay and increasing the time between retries. The delay grows larger after each attempt, but will not exceed 15 minutes. The system will attempt up to 100 times, and after that, no further retries will occur.
## Webhook Validation
Webhooks include an x-signature header, which enables servers to authenticate the request using a secret key specific to your server setup.
### Signature Calculation with ECDSA
The process to calculate and verify the signature using ECDSA
:::warning[Capture the raw payload]
Do not parse the incoming HTTP request into an object or re-serialize it. Instead, capture the raw payload as a string to ensure that the signature verification process is accurate.
:::
```java
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
public class JavaSignatureExample {
public static void main(String[] args) throws Exception {
// If you don't already have BouncyCastle as a provider, add it
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(new BouncyCastleProvider());
}
// Public key provided from webhook configuration
String publicKeyBase64 = "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAExn8LhKa3YnVvGHeyT+siyu9+B5knDRtigP4R08nw7Fp0lbXtwoiAO1N0LOj7k39JY5iM385BJrRV2u5Y4N0Qxg==";
// From X-Signature header
String signatureBase64 = "MEYCIQCtvKgMTivqsT3S2G3qD46lK0+FD7ECW4dK2MtaivfWvwIhALJly6ZqemabK+gYGNWpZACzj1ApJ6immVuIQ0MxONXV";
// Body of the request
String body = "hello world";
KeyFactory keyFactory = KeyFactory.getInstance("ECDSA", "BC");
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(Base64.decode(publicKeyBase64));
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
Signature signature = Signature.getInstance("SHA256withECDSA");
signature.initVerify(publicKey);
// The body of the request
signature.update(body.getBytes());
boolean verified = signature.verify(Base64.decode(signatureBase64));
System.out.println("Signature is valid: " + verified);
}
}
```
```php
getMessage() . "\n";
return false;
}
}
// Run the example
verifyECDSASignatureExample();
?>
```
```python
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
import base64
# Replace this with your webhook public key
public_key_base64 = "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAExn8LhKa3YnVvGHeyT+siyu9+B5knDRtigP4R08nw7Fp0lbXtwoiAO1N0LOj7k39JY5iM385BJrRV2u5Y4N0Qxg=="
# This comes from X-Signature header
signature_base64 = "MEYCIQCtvKgMTivqsT3S2G3qD46lK0+FD7ECW4dK2MtaivfWvwIhALJly6ZqemabK+gYGNWpZACzj1ApJ6immVuIQ0MxONXV"
# This is the body of the request sent to you
message = b"hello world"
# Load the public key from the base64-encoded string
public_key_loaded = serialization.load_der_public_key(
base64.b64decode(public_key_base64)
)
# Verify the signature using the loaded public key
try:
public_key_loaded.verify(
base64.b64decode(signature_base64),
message,
ec.ECDSA(hashes.SHA256())
)
print("\nSignature verification succeeded!")
except InvalidSignature:
print("\nSignature verification failed!")
```
***
## Webhook details
For the detailed description of webhooks and their fields, see the [Layer1 webhook reference](/l1-api/webhook-events/crypto-address-created).
## Webhooks
### layer1:crypto:address:created
The webhook notification sends a JSON object containing details about the event. Here is an example payload:
```json title="Address Created"
{
"event": "layer1:crypto:address:created",
"timestamp": "2024-04-30T15:57:30.399031710Z",
"data": {
"id": "37729aed-4e03-46d5-b695-d964da0ae29e",
"address": "0x157e511931f57d7d41d098b5bebcf8ebfc2b87d8",
"network": "ETHEREUM",
"keyPairId": "85cbb05e-8ffe-4b09-ba7d-4c41dfeb551c",
"reference": "d427cc5f-20d1-4e6d-93b0-454a6cb45cd8",
"assetPoolId": "e0579d29-987a-440c-b78b-26067b49bc96"
}
}
```
Below is a detailed explanation of each field in the webhook payload:
| Field | Type | Description |
| ------------------ | ------ | ------------------------------------------------------------------------------ |
| `event` | string | Identifies the type of event. Example: "layer1:crypto:address:created" |
| `timestamp` | string | ISO 8601 formatted date and time when the event was generated. |
| `data` | object | Contains detailed information about the event. |
| `data.id` | string | Unique identifier for the event, in GUID format. |
| `data.address` | string | The cryptocurrency address that was created. |
| `data.network` | string | The network for the cryptocurrency address. |
| `data.keyPairId` | string | Identifier for the key pair associated with the address, in GUID format. |
| `data.reference` | string | A reference identifier linked to the creation of this address, in GUID format. |
| `data.assetPoolId` | string | Identifier of the asset pool this address was added to, in GUID format. |
***
### layer1:crypto:deposit:status-change
The webhook notification sends a JSON object containing details about the event. Here is the example payloads:
```json
{
event: "layer1:crypto:deposit:status-change",
timestamp: "2024-05-01T11:55:23.676068369Z",
data: {
id: "018f3402631176798ab2d431929f53b938d51d4b3ef64c809cb73eb884cc7c306f",
address: {
id: "38d51d4b-3ef6-4c80-9cb7-3eb884cc7c30",
address: "0xf6483454996bce404d62656eb3cbcf87232dc153",
assetPoolId: "e0579d29-987a-440c-b78b-26067b49bc96",
network: "ETHEREUM",
keyPairId: "173cfb47-3ce1-4550-98bc-2da2f1cb866b",
reference: "fcd4687e-5bc7-41e0-ba5a-d2f13a857ee7",
},
reference: null,
status: "DETECTED",
sources: [
{
address: "0xae9e8a1235e1113f936a38492c70fbb4ee297cdb",
amount: "0.001",
},
],
destinations: [
{
address: "0xf6483454996bce404d62656eb3cbcf87232dc153",
amount: "0.001",
},
],
type: "DEPOSIT",
asset: "ETH",
amount: "0.001",
hash: "0x5fee8f91e72d6baba7b95af5a031ea3b3e1ae527009f401b5403a3c2f3294530",
networkDetail: null,
metadata: null,
createdAt: "2024-05-01T11:55:22.769Z",
updatedAt: "2024-05-01T11:55:23.545773Z",
},
};
```
```json
{
event: "layer1:crypto:deposit:status-change",
timestamp: "2024-05-01T11:55:28.579411700Z",
data: {
id: "018f3402631176798ab2d431929f53b938d51d4b3ef64c809cb73eb884cc7c306f",
address: {
id: "38d51d4b-3ef6-4c80-9cb7-3eb884cc7c30",
address: "0xf6483454996bce404d62656eb3cbcf87232dc153",
assetPoolId: "e0579d29-987a-440c-b78b-26067b49bc96",
network: "ETHEREUM",
keyPairId: "173cfb47-3ce1-4550-98bc-2da2f1cb866b",
reference: "fcd4687e-5bc7-41e0-ba5a-d2f13a857ee7",
},
reference: null,
status: "UNCONFIRMED",
sources: [
{
address: "0xae9e8a1235e1113f936a38492c70fbb4ee297cdb",
amount: "0.000038485390062",
},
{
address: "0xae9e8a1235e1113f936a38492c70fbb4ee297cdb",
amount: "0.001",
},
],
destinations: [
{
address: "0xf6483454996bce404d62656eb3cbcf87232dc153",
amount: "0.001",
},
],
type: "DEPOSIT",
asset: "ETH",
amount: "0.001",
hash: "0x5fee8f91e72d6baba7b95af5a031ea3b3e1ae527009f401b5403a3c2f3294530",
networkDetail: {
block: { number: 5814953, timestamp: "2024-05-01T11:55:24Z" },
fee: { amount: "0.000038485390062", asset: "ETH" },
},
metadata: null,
createdAt: "2024-05-01T11:55:22.769Z",
updatedAt: "2024-05-01T11:55:28.535125Z",
},
};
```
```json
{
event: "layer1:crypto:deposit:status-change",
timestamp: "2024-05-01T11:55:28.579411700Z",
data: {
id: "018f3402631176798ab2d431929f53b938d51d4b3ef64c809cb73eb884cc7c306f",
address: {
id: "38d51d4b-3ef6-4c80-9cb7-3eb884cc7c30",
address: "0xf6483454996bce404d62656eb3cbcf87232dc153",
assetPoolId: "e0579d29-987a-440c-b78b-26067b49bc96",
network: "ETHEREUM",
keyPairId: "173cfb47-3ce1-4550-98bc-2da2f1cb866b",
reference: "fcd4687e-5bc7-41e0-ba5a-d2f13a857ee7",
},
reference: null,
status: "CONFIRMED",
sources: [
{
address: "0xae9e8a1235e1113f936a38492c70fbb4ee297cdb",
amount: "0.000038485390062",
},
{
address: "0xae9e8a1235e1113f936a38492c70fbb4ee297cdb",
amount: "0.001",
},
],
destinations: [
{
address: "0xf6483454996bce404d62656eb3cbcf87232dc153",
amount: "0.001",
},
],
type: "DEPOSIT",
asset: "ETH",
amount: "0.001",
hash: "0x5fee8f91e72d6baba7b95af5a031ea3b3e1ae527009f401b5403a3c2f3294530",
networkDetail: {
block: { number: 5814953, timestamp: "2024-05-01T11:55:24Z" },
fee: { amount: "0.000038485390062", asset: "ETH" },
},
metadata: null,
createdAt: "2024-05-01T11:55:22.769Z",
updatedAt: "2024-05-01T11:55:28.535125Z",
},
};
```
```json
{
event: "layer1:crypto:deposit:status-change",
timestamp: "2024-05-01T11:56:29.064464799Z",
data: {
id: "018f3402631176798ab2d431929f53b938d51d4b3ef64c809cb73eb884cc7c306f",
address: {
id: "38d51d4b-3ef6-4c80-9cb7-3eb884cc7c30",
address: "0xf6483454996bce404d62656eb3cbcf87232dc153",
assetPoolId: "e0579d29-987a-440c-b78b-26067b49bc96",
network: "ETHEREUM",
keyPairId: "173cfb47-3ce1-4550-98bc-2da2f1cb866b",
reference: "fcd4687e-5bc7-41e0-ba5a-d2f13a857ee7",
},
reference: null,
status: "SCREENING_REQUESTED",
sources: [
{
address: "0xae9e8a1235e1113f936a38492c70fbb4ee297cdb",
amount: "0.000038485390062",
},
{
address: "0xae9e8a1235e1113f936a38492c70fbb4ee297cdb",
amount: "0.001",
},
],
destinations: [
{
address: "0xf6483454996bce404d62656eb3cbcf87232dc153",
amount: "0.001",
},
],
type: "DEPOSIT",
asset: "ETH",
amount: "0.001",
hash: "0x5fee8f91e72d6baba7b95af5a031ea3b3e1ae527009f401b5403a3c2f3294530",
networkDetail: {
block: { number: 5814953, timestamp: "2024-05-01T11:55:24Z" },
fee: { amount: "0.000038485390062", asset: "ETH" },
},
metadata: null,
createdAt: "2024-05-01T11:55:22.769Z",
updatedAt: "2024-05-01T11:56:28.994764Z",
},
};
```
```json
{
event: "layer1:crypto:deposit:status-change",
timestamp: "2024-05-01T11:56:36.625504360Z",
data: {
id: "018f3402631176798ab2d431929f53b938d51d4b3ef64c809cb73eb884cc7c306f",
address: {
id: "38d51d4b-3ef6-4c80-9cb7-3eb884cc7c30",
address: "0xf6483454996bce404d62656eb3cbcf87232dc153",
assetPoolId: "e0579d29-987a-440c-b78b-26067b49bc96",
network: "ETHEREUM",
keyPairId: "173cfb47-3ce1-4550-98bc-2da2f1cb866b",
reference: "fcd4687e-5bc7-41e0-ba5a-d2f13a857ee7",
},
reference: null,
status: "SUCCESS",
sources: [
{
address: "0xae9e8a1235e1113f936a38492c70fbb4ee297cdb",
amount: "0.000038485390062",
},
{
address: "0xae9e8a1235e1113f936a38492c70fbb4ee297cdb",
amount: "0.001",
},
],
destinations: [
{
address: "0xf6483454996bce404d62656eb3cbcf87232dc153",
amount: "0.001",
},
],
type: "DEPOSIT",
asset: "ETH",
amount: "0.001",
hash: "0x5fee8f91e72d6baba7b95af5a031ea3b3e1ae527009f401b5403a3c2f3294530",
networkDetail: {
block: { number: 5814953, timestamp: "2024-05-01T11:55:24Z" },
fee: { amount: "0.000038485390062", asset: "ETH" },
},
metadata: null,
createdAt: "2024-05-01T11:55:22.769Z",
updatedAt: "2024-05-01T11:56:36.513590Z",
},
};
```
Below is a detailed explanation of each field in the webhook payload:
| Field | Type | Description |
| -------------------------- | ----------- | ----------------------------------------------------------------------------------------------- |
| `event` | string | Identifies the type of event. Example: "layer1:crypto:deposit:status-change" |
| `timestamp` | string | ISO 8601 formatted date and time when the event was generated. |
| `data` | object | Contains detailed information about the event. |
| `data.id` | string | Unique identifier for the event. |
| `data.address` | object | Detailed information about the address involved in the deposit. |
| `data.address.id` | string | Unique identifier for the address object, in GUID format. |
| `data.address.address` | string | The cryptocurrency address involved. |
| `data.address.assetPoolId` | string | Identifier of the asset pool this address belongs to, in GUID format. |
| `data.address.network` | string | The network for the cryptocurrency address |
| `data.address.keyPairId` | string | Identifier for the key pair associated with the address, in GUID format. |
| `data.address.reference` | string | A reference identifier linked to the creation of this address. |
| `data.reference` | null/string | A general reference identifier, can be null. |
| `data.status` | string | Current status of the deposit [DETECTED, UNCONFIRMED, CONFIRMED, SCREENING_REQUESTED, SUCCESS]. |
| `data.sources[]` | array | Array of source addresses and amounts involved in the transaction. |
| `data.destinations[]` | array | Array of destination addresses and amounts involved in the transaction. |
| `data.type` | string | Type of transaction. |
| `data.asset` | string | Type of asset involved in the transaction. |
| `data.amount` | string | Amount of the asset involved in the transaction. |
| `data.hash` | string | Transaction hash. |
| `data.networkDetail` | null | Additional network-specific details, can be null. |
| `data.metadata` | null | Additional metadata related to the event, can be null. |
| `data.createdAt` | string | ISO 8601 formatted date and time when the data was created. |
| `data.updatedAt` | string | ISO 8601 formatted date and time when the data was last updated. |
***
### layer1:crypto:withdrawal:status-change
The webhook notification sends a JSON object containing details about the event. Here is the example payloads:
```json
{
"event": "layer1:crypto:withdrawal:status-change",
"timestamp": "2024-05-01T14:58:57.756642777Z",
"data": {
"id": "018f34aa753176e1a746377665057346aa2eadaf62374e3fb43529de7c4be91969",
"address": {
"id": "aa2eadaf-6237-4e3f-b435-29de7c4be919",
"address": "0x2ec9ded3d63d2d9c30b72c31d267e207d7242394",
"assetPoolId": "e0579d29-987a-440c-b78b-26067b49bc96",
"network": "ETHEREUM",
"keyPairId": "a2dabc1a-9b36-40dd-b236-2e542fec155c",
"reference": "085aa928-01b6-407b-82d8-2b9975cbc250"
},
"reference": "28b262d9-1f52-4ee2-9f94-addd9762d23c",
"status": "CREATED",
"sources": [
{
"address": "0x2ec9ded3d63d2d9c30b72c31d267e207d7242394",
"amount": "0.000117806730763928"
},
{
"address": "0x2ec9ded3d63d2d9c30b72c31d267e207d7242394",
"amount": "0.001"
}
],
"destinations": null,
"type": "WITHDRAWAL",
"asset": "ETH",
"amount": "0.001",
"hash": null,
"networkDetail": null,
"metadata": null,
"createdAt": "2024-05-01T14:58:57.457Z",
"updatedAt": "2024-05-01T14:58:57.736442Z"
}
}
```
```json
{
"event": "layer1:crypto:withdrawal:status-change",
"timestamp": "2024-05-01T14:58:58.491311736Z",
"data": {
"id": "018f34aa753176e1a746377665057346aa2eadaf62374e3fb43529de7c4be91966",
"address": {
"id": "aa2eadaf-6237-4e3f-b435-29de7c4be919",
"address": "0x2ec9ded3d63d2d9c30b72c31d267e207d7242394",
"assetPoolId": "e0579d29-987a-440c-b78b-26067b49bc96",
"network": "ETHEREUM",
"keyPairId": "a2dabc1a-9b36-40dd-b236-2e542fec155c",
"reference": "085aa928-01b6-407b-82d8-2b9975cbc250"
},
"reference": "28b262d9-1f52-4ee2-9f94-addd9762d23c",
"status": "SIGNED",
"sources": [
{
"address": "0x2ec9ded3d63d2d9c30b72c31d267e207d7242394",
"amount": "0.000117806730763928"
},
{
"address": "0x2ec9ded3d63d2d9c30b72c31d267e207d7242394",
"amount": "0.001"
}
],
"destinations": null,
"type": "WITHDRAWAL",
"asset": "ETH",
"amount": "0.000117806730763928",
"hash": null,
"networkDetail": null,
"metadata": null,
"createdAt": "2024-05-01T14:58:57.457Z",
"updatedAt": "2024-05-01T14:58:58.366656Z"
}
}
```
```json
{
"event": "layer1:crypto:withdrawal:status-change",
"timestamp": "2024-05-01T14:59:02.359359108Z",
"data": {
"id": "018f34aa753176e1a746377665057346aa2eadaf62374e3fb43529de7c4be91969",
"address": {
"id": "aa2eadaf-6237-4e3f-b435-29de7c4be919",
"address": "0x2ec9ded3d63d2d9c30b72c31d267e207d7242394",
"assetPoolId": "e0579d29-987a-440c-b78b-26067b49bc96",
"network": "ETHEREUM",
"keyPairId": "a2dabc1a-9b36-40dd-b236-2e542fec155c",
"reference": "085aa928-01b6-407b-82d8-2b9975cbc250"
},
"reference": "28b262d9-1f52-4ee2-9f94-addd9762d23c",
"status": "UNCONFIRMED",
"sources": [
{
"address": "0x2ec9ded3d63d2d9c30b72c31d267e207d7242394",
"amount": "0.000114500286264"
},
{
"address": "0x2ec9ded3d63d2d9c30b72c31d267e207d7242394",
"amount": "0.001"
}
],
"destinations": [
{
"address": "0xae9e8a1235e1113f936a38492c70fbb4ee297cdb",
"amount": "0.001"
}
],
"type": "WITHDRAWAL",
"asset": "ETH",
"amount": "0.001",
"hash": "0xb0d49a30ebe1c84e89a89c52579ae06639dc22d39fd8bb91bfa6dc0130bd09ae",
"networkDetail": {
"block": {
"number": 5815735,
"timestamp": "2024-05-01T14:59:00Z"
},
"fee": {
"amount": "0.000114500286264",
"asset": "ETH"
}
},
"metadata": null,
"createdAt": "2024-05-01T14:58:57.457Z",
"updatedAt": "2024-05-01T14:59:02.331458Z"
}
}
```
```json
{
"event": "layer1:crypto:withdrawal:status-change",
"timestamp": "2024-05-01T14:59:50.096614363Z",
"data": {
"id": "018f34aa753176e1a746377665057346aa2eadaf62374e3fb43529de7c4be91966",
"address": {
"id": "aa2eadaf-6237-4e3f-b435-29de7c4be919",
"address": "0x2ec9ded3d63d2d9c30b72c31d267e207d7242394",
"assetPoolId": "e0579d29-987a-440c-b78b-26067b49bc96",
"network": "ETHEREUM",
"keyPairId": "a2dabc1a-9b36-40dd-b236-2e542fec155c",
"reference": "085aa928-01b6-407b-82d8-2b9975cbc250"
},
"reference": "28b262d9-1f52-4ee2-9f94-addd9762d23c",
"status": "CONFIRMED",
"sources": [
{
"address": "0x2ec9ded3d63d2d9c30b72c31d267e207d7242394",
"amount": "0.000114500286264"
},
{
"address": "0x2ec9ded3d63d2d9c30b72c31d267e207d7242394",
"amount": "0.001"
}
],
"destinations": [
{
"address": "0xae9e8a1235e1113f936a38492c70fbb4ee297cdb",
"amount": "0.001"
}
],
"type": "WITHDRAWAL",
"asset": "ETH",
"amount": "0.000114500286264",
"hash": "0xb0d49a30ebe1c84e89a89c52579ae06639dc22d39fd8bb91bfa6dc0130bd09ae",
"networkDetail": {
"block": {
"number": 5815735,
"timestamp": "2024-05-01T14:59:00Z"
},
"fee": {
"amount": "0.000114500286264",
"asset": "ETH"
}
},
"metadata": null,
"createdAt": "2024-05-01T14:58:57.457Z",
"updatedAt": "2024-05-01T14:59:50.060099Z"
}
}
```
Below is a detailed explanation of each field in the webhook payload:
| Field | Type | Description |
| ------------------------------------ | ------ | ---------------------------------------------------------------------------- |
| `event` | string | Identifies the type of event, for example, "layer1:crypto:withdrawal:status-change" |
| `timestamp` | string | ISO 8601 formatted date and time when the event was generated. |
| `data` | object | Contains detailed information about the event. |
| `data.id` | string | Unique identifier for the event. |
| `data.address` | object | Details of the address involved in the withdrawal. |
| `data.address.id` | string | Unique identifier for the address object. |
| `data.address.address` | string | The cryptocurrency address involved. |
| `data.address.assetPoolId` | string | Identifier of the asset pool this address belongs to, in GUID format. |
| `data.address.network` | string | The network for the cryptocurrency address. |
| `data.address.keyPairId` | string | Identifier for the key pair associated with the address, in GUID format. |
| `data.address.reference` | string | A reference identifier linked to the address. |
| `data.reference` | string | A general reference identifier for the withdrawal. |
| `data.status` | string | Current status of the withdrawal [CREATED, SIGNED,UNCONFIRMED, CONFIRMED]. |
| `data.sources[]` | array | Array of source addresses and amounts involved in the withdrawal. |
| `data.destinations[]` | array | Array of destination addresses and amounts being transferred. |
| `data.type` | string | Type of transaction. |
| `data.asset` | string | Type of asset involved in the transaction. |
| `data.amount` | string | Total amount of the asset being withdrawn. |
| `data.hash` | string | Transaction hash. |
| `data.networkDetail` | object | Additional details about the network state at the time of the transaction. |
| `data.networkDetail.block` | object | Block information relevant to the transaction. |
| `data.networkDetail.block.number` | string | Block number associated with the transaction. |
| `data.networkDetail.block.timestamp` | string | Timestamp of the block. |
| `data.networkDetail.fee` | object | Fee information related to the transaction. |
| `data.networkDetail.fee.amount` | string | Amount of the fee charged. |
| `data.networkDetail.fee.asset` | string | Asset type of the fee (for example, ETH). |
| `data.metadata` | null | Additional metadata related to the event (if any). |
| `data.createdAt` | string | ISO 8601 formatted date and time when the data was created. |
| `data.updatedAt` | string | ISO 8601 formatted date and time when the data was last updated. |
***
### layer1:crypto:consolidation:status-change
The webhook notification sends a JSON object containing details about the event. Here is an example payload:
```json
{
"event": "layer1:crypto:consolidation:status-change",
"timestamp": "2024-05-09T06:11:13.976035981Z",
"data": {
"id": "018f5bfa2e9a74edbcbb60fccbe7e842a4e464fb30554fe2a5cb4ed39a14408969",
"address": {
"id": "a4e464fb-3055-4fe2-a5cb-4ed39a144089",
"address": "0x1bade4a7ab3f43ef98ee90045d72f70031904c84",
"assetPoolId": "b85cdc60-29da-4b2a-b28c-248e38cef764",
"network": "ETHEREUM",
"keyPairId": "59d0e545-2985-46c2-a834-7ab5199abbd4",
"reference": "REF-127558124LT181Y1I"
},
"reference": "consolidation-018f5bf8-f9c3-77bb-abc7-0b0597001ba1",
"status": "CREATED",
"sources": [
{
"address": "0x1bade4a7ab3f43ef98ee90045d72f70031904c84",
"amount": "0.00050889650541582",
"asset": "ETH"
},
{
"address": "0x1bade4a7ab3f43ef98ee90045d72f70031904c84",
"amount": "100.00",
"asset": "USDT"
}
],
"destinations": [
{
"address": "0xd399fe8c3914c9dc9f218c912c264d616e32a5ab",
"amount": "100.00",
"asset": "USDT"
}
],
"type": "CONSOLIDATION",
"asset": "USDT",
"amount": "100.00",
"createdAt": "2024-05-09T06:11:13.690Z",
"updatedAt": "2024-05-09T06:11:13.958481Z"
}
}
```
```json
{
"event": "layer1:crypto:consolidation:status-change",
"timestamp": "2024-05-09T06:11:14.208226824Z",
"data": {
"id": "018f5bfa2e9a74edbcbb60fccbe7e842a4e464fb30554fe2a5cb4ed39a14408969",
"address": {
"id": "a4e464fb-3055-4fe2-a5cb-4ed39a144089",
"address": "0x1bade4a7ab3f43ef98ee90045d72f70031904c84",
"assetPoolId": "b85cdc60-29da-4b2a-b28c-248e38cef764",
"network": "ETHEREUM",
"keyPairId": "59d0e545-2985-46c2-a834-7ab5199abbd4",
"reference": "REF-127558124LT181Y1I"
},
"reference": "consolidation-018f5bf8-f9c3-77bb-abc7-0b0597001ba1",
"status": "SIGNED",
"sources": [
{
"address": "0x1bade4a7ab3f43ef98ee90045d72f70031904c84",
"amount": "0.00050889650541582",
"asset": "ETH"
},
{
"address": "0x1bade4a7ab3f43ef98ee90045d72f70031904c84",
"amount": "100.00",
"asset": "USDT"
}
],
"destinations": [
{
"address": "0xd399fe8c3914c9dc9f218c912c264d616e32a5ab",
"amount": "100.00",
"asset": "USDT"
}
],
"type": "CONSOLIDATION",
"asset": "USDT",
"amount": "100.00",
"createdAt": "2024-05-09T06:11:13.690Z",
"updatedAt": "2024-05-09T06:11:14.188735Z"
}
}
```
```json
{
"event": "layer1:crypto:consolidation:status-change",
"timestamp": "2024-05-09T06:11:14.747080547Z",
"data": {
"id": "018f5bfa2e9a74edbcbb60fccbe7e842a4e464fb30554fe2a5cb4ed39a14408969",
"address": {
"id": "a4e464fb-3055-4fe2-a5cb-4ed39a144089",
"address": "0x1bade4a7ab3f43ef98ee90045d72f70031904c84",
"assetPoolId": "b85cdc60-29da-4b2a-b28c-248e38cef764",
"network": "ETHEREUM",
"keyPairId": "59d0e545-2985-46c2-a834-7ab5199abbd4",
"reference": "REF-127558124LT181Y1I"
},
"reference": "consolidation-018f5bf8-f9c3-77bb-abc7-0b0597001ba1",
"status": "DETECTED",
"sources": [
{
"address": "0x1bade4a7ab3f43ef98ee90045d72f70031904c84",
"amount": "100.00"
}
],
"destinations": [
{
"address": "0xd399fe8c3914c9dc9f218c912c264d616e32a5ab",
"amount": "100.00"
}
],
"type": "CONSOLIDATION",
"asset": "USDT",
"amount": "100.00",
"hash": "0x74b832f44c3405846bd9a881feca9f9903bf78cca42d5706bc1cec6bc7871685",
"createdAt": "2024-05-09T06:11:13.690Z",
"updatedAt": "2024-05-09T06:11:14.726244Z"
}
}
```
```json
{
"event": "layer1:crypto:consolidation:status-change",
"timestamp": "2024-05-09T06:11:29.376821677Z",
"data": {
"id": "018f5bfa2e9a74edbcbb60fccbe7e842a4e464fb30554fe2a5cb4ed39a14408969",
"address": {
"id": "a4e464fb-3055-4fe2-a5cb-4ed39a144089",
"address": "0x1bade4a7ab3f43ef98ee90045d72f70031904c84",
"assetPoolId": "b85cdc60-29da-4b2a-b28c-248e38cef764",
"network": "ETHEREUM",
"keyPairId": "59d0e545-2985-46c2-a834-7ab5199abbd4",
"reference": "REF-127558124LT181Y1I"
},
"reference": "consolidation-018f5bf8-f9c3-77bb-abc7-0b0597001ba1",
"status": "UNCONFIRMED",
"sources": [
{
"address": "0x1bade4a7ab3f43ef98ee90045d72f70031904c84",
"amount": "0.00023986962523329",
"asset": "ETH"
},
{
"address": "0x1bade4a7ab3f43ef98ee90045d72f70031904c84",
"amount": "100.00",
"asset": "USDT"
}
],
"destinations": [
{
"address": "0xd399fe8c3914c9dc9f218c912c264d616e32a5ab",
"amount": "100.00",
"asset": "USDT"
}
],
"type": "CONSOLIDATION",
"asset": "USDT",
"amount": "100.00",
"hash": "0x74b832f44c3405846bd9a881feca9f9903bf78cca42d5706bc1cec6bc7871685",
"networkDetail": {
"block": {
"number": 5865626,
"timestamp": "2024-05-09T06:11:24Z"
},
"fee": {
"amount": "0.00023986962523329",
"asset": "ETH"
}
},
"createdAt": "2024-05-09T06:11:13.690Z",
"updatedAt": "2024-05-09T06:11:29.352098Z"
}
}
```
```json
{
"event": "layer1:crypto:consolidation:status-change",
"timestamp": "2024-05-09T06:12:29.567534241Z",
"data": {
"id": "018f5bfa2e9a74edbcbb60fccbe7e842a4e464fb30554fe2a5cb4ed39a14408969",
"address": {
"id": "a4e464fb-3055-4fe2-a5cb-4ed39a144089",
"address": "0x1bade4a7ab3f43ef98ee90045d72f70031904c84",
"assetPoolId": "b85cdc60-29da-4b2a-b28c-248e38cef764",
"network": "ETHEREUM",
"keyPairId": "59d0e545-2985-46c2-a834-7ab5199abbd4",
"reference": "REF-127558124LT181Y1I"
},
"reference": "consolidation-018f5bf8-f9c3-77bb-abc7-0b0597001ba1",
"status": "CONFIRMED",
"sources": [
{
"address": "0x1bade4a7ab3f43ef98ee90045d72f70031904c84",
"amount": "100.00",
"asset": "USDT"
},
{
"address": "0x1bade4a7ab3f43ef98ee90045d72f70031904c84",
"amount": "0.00023986962523329",
"asset": "ETH"
}
],
"destinations": [
{
"address": "0xd399fe8c3914c9dc9f218c912c264d616e32a5ab",
"amount": "100.00",
"asset": "USDT"
}
],
"type": "CONSOLIDATION",
"asset": "USDT",
"amount": "100.00",
"hash": "0x74b832f44c3405846bd9a881feca9f9903bf78cca42d5706bc1cec6bc7871685",
"networkDetail": {
"block": {
"number": 5865626,
"timestamp": "2024-05-09T06:11:24Z"
},
"fee": {
"amount": "0.00023986962523329",
"asset": "ETH"
}
},
"createdAt": "2024-05-09T06:11:13.690Z",
"updatedAt": "2024-05-09T06:12:29.545914Z"
}
}
```
```json
{
"event": "layer1:crypto:consolidation:status-change",
"timestamp": "2024-05-09T06:12:29.702705023Z",
"data": {
"id": "018f5bfa2e9a74edbcbb60fccbe7e842a4e464fb30554fe2a5cb4ed39a14408969",
"address": {
"id": "a4e464fb-3055-4fe2-a5cb-4ed39a144089",
"address": "0x1bade4a7ab3f43ef98ee90045d72f70031904c84",
"assetPoolId": "b85cdc60-29da-4b2a-b28c-248e38cef764",
"network": "ETHEREUM",
"keyPairId": "59d0e545-2985-46c2-a834-7ab5199abbd4",
"reference": "REF-127558124LT181Y1I"
},
"reference": "consolidation-018f5bf8-f9c3-77bb-abc7-0b0597001ba1",
"status": "SUCCESS",
"sources": [
{
"address": "0x1bade4a7ab3f43ef98ee90045d72f70031904c84",
"amount": "100.00",
"asset": "USDT"
},
{
"address": "0x1bade4a7ab3f43ef98ee90045d72f70031904c84",
"amount": "0.00023986962523329",
"asset": "ETH"
}
],
"destinations": [
{
"address": "0xd399fe8c3914c9dc9f218c912c264d616e32a5ab",
"amount": "100.00",
"asset": "USDT"
}
],
"type": "CONSOLIDATION",
"asset": "USDT",
"amount": "100.00",
"hash": "0x74b832f44c3405846bd9a881feca9f9903bf78cca42d5706bc1cec6bc7871685",
"networkDetail": {
"block": {
"number": 5865626,
"timestamp": "2024-05-09T06:11:24Z"
},
"fee": {
"amount": "0.00023986962523329",
"asset": "ETH"
}
},
"createdAt": "2024-05-09T06:11:13.690Z",
"updatedAt": "2024-05-09T06:12:29.680325Z"
}
}
```
Below is a detailed explanation of each field in the webhook payload:
| Field | Type | Description |
| ------------------------------------ | ------ | ------------------------------------------------------------------------------------------------- |
| `event` | string | Identifies the type of event, for example, "layer1:crypto:consolidation:status-change" |
| `timestamp` | string | ISO 8601 formatted date and time when the event was generated. |
| `data` | object | Contains detailed information about the event. |
| `data.id` | string | Unique identifier for the event. |
| `data.address` | object | Details of the address involved in the consolidation. |
| `data.address.id` | string | Unique identifier for the address object. |
| `data.address.address` | string | The cryptocurrency address involved. |
| `data.address.assetPoolId` | string | Identifier of the asset pool this address belongs to, in GUID format. |
| `data.address.network` | string | The network for the cryptocurrency address. |
| `data.address.keyPairId` | string | Identifier for the key pair associated with the address, in GUID format. |
| `data.address.reference` | string | A reference identifier linked to the address. |
| `data.reference` | string | A general reference identifier for the consolidation event. |
| `data.status` | string | Current status of the consolidation [CREATED, SIGNED, DETECTED, UNCONFIRMED, CONFIRMED, SUCCESS]. |
| `data.sources[]` | array | Array of source addresses and amounts involved in the consolidation. |
| `data.sources[].address` | string | The source address involved in the transaction. |
| `data.sources[].amount` | string | The amount of the asset being transferred from the source address. |
| `data.sources[].asset` | string | Type of asset being transferred. |
| `data.destinations[]` | array | Array of destination addresses and amounts being transferred. |
| `data.destinations[].address` | string | The destination address involved in the transaction. |
| `data.destinations[].amount` | string | The amount of the asset being transferred to the destination address. |
| `data.destinations[].asset` | string | Type of asset being transferred. |
| `data.type` | string | Type of transaction. |
| `data.asset` | string | Type of asset involved in the transaction. |
| `data.amount` | string | Total amount of the asset being consolidated. |
| `data.hash` | string | Transaction hash. |
| `data.networkDetail` | object | Additional details about the network state at the time of the transaction. |
| `data.networkDetail.block` | object | Block information relevant to the transaction. |
| `data.networkDetail.block.number` | string | Block number associated with the transaction. |
| `data.networkDetail.block.timestamp` | string | Timestamp of the block. |
| `data.networkDetail.fee` | object | Fee information related to the transaction. |
| `data.networkDetail.fee.amount` | string | Amount of the fee charged. |
| `data.networkDetail.fee.asset` | string | Asset type of the fee. |
| `data.createdAt` | string | ISO 8601 formatted date and time when the data was created. |
| `data.updatedAt` | string | ISO 8601 formatted date and time when the data was last updated. |
***
### layer1:trade:conversion:created
The webhook notification sends a JSON object containing details about the event. Here is an example payload:
```json title="Conversion - CREATED"
{
"event": "layer1:trade:conversion:created",
"timestamp": null,
"data": {
"id": "65a726c7-e56f-4bf0-b65e-7225d8abd9ee",
"reference": "13963121-6626-4694-8fb8-9557db7b5a04",
"status": "PENDING",
"from": {
"asset": "EUR",
"amount": 100
},
"to": {
"asset": "LTC",
"amount": 1.386770212175842462
},
"orders": null,
"router": "VWAP",
"executor": "MAKER",
"movements": null,
"createdAt": null
}
}
```
Below is a detailed explanation of each field in the webhook payload:
| Field | Type | Description |
| ------------------ | ------ | ---------------------------------------------------------------------- |
| `event` | string | Identifies the type of event, for example, "layer1:trade:conversion:created". |
| `timestamp` | null | The time when the event was generated, can be null. |
| `data` | object | Contains detailed information about the event. |
| `data.id` | string | Unique identifier for the conversion event, in GUID format. |
| `data.reference` | string | A reference identifier linked to the conversion, in GUID format. |
| `data.status` | string | Current status of the conversion a the time of creation. |
| `data.from` | object | Details about the currency from which is converted. |
| `data.from.asset` | string | Type of asset being converted from. |
| `data.from.amount` | number | Amount of the asset being converted. |
| `data.to` | object | Details about the currency to which is converted. |
| `data.to.asset` | string | Type of asset being converted to. |
| `data.to.amount` | number | Amount of the asset after conversion. |
| `data.orders` | null | Details of any orders involved in the conversion, can be null. |
| `data.router` | string | Routing mechanism used for the conversion [VWAP]. |
| `data.executor` | string | Execution type for the conversion [MAKER]. |
| `data.movements` | null | Any movements related to the conversion, can be null. |
| `data.createdAt` | null | The time when the conversion was created, can be null. |
***
### layer1:trade:conversion:state-changed
The webhook notification sends a JSON object containing details about the event. Here is the example payloads:
```json
{
"event": "layer1:trade:conversion:state-changed",
"timestamp": "2024-05-01T14:49:43.151138Z",
"data": {
"id": "65a726c7-e56f-4bf0-b65e-7225d8abd9ee",
"reference": "13963121-6626-4694-8fb8-9557db7b5a04",
"status": "PLACED",
"from": {
"asset": "EUR",
"amount": 100
},
"to": {
"asset": "LTC",
"amount": 1.386770212175842462
},
"orders": [
{
"symbol": "LTC-EUR",
"side": "BUY",
"targetPrice": 72.11,
"targetQuantity": 1.386770212175842462,
"venue": "kraken",
"output": 0,
"id": "234098b4-f55b-4ef6-9ea3-1d28010d8476",
"venueOrderId": "cd5009ad-ecd4-438a-813c-7608c85833d7",
"status": "PLACED",
"actualPrice": 0,
"actualQuantity": 0,
"createdAt": "2024-05-01T14:49:43.157286Z"
}
],
"router": "VWAP",
"executor": "MAKER",
"movements": [],
"createdAt": "2024-05-01T14:49:43.151138Z"
}
}
```
```json
{
"event": "layer1:trade:conversion:state-changed",
"timestamp": "2024-05-01T14:49:43.151138Z",
"data": {
"id": "65a726c7-e56f-4bf0-b65e-7225d8abd9ee",
"reference": "13963121-6626-4694-8fb8-9557db7b5a04",
"status": "COMPLETED",
"from": {
"asset": "EUR",
"amount": 100
},
"to": {
"asset": "LTC",
"amount": 1.386770212175842462
},
"orders": [
{
"symbol": "LTC-EUR",
"side": "BUY",
"targetPrice": 72.11,
"targetQuantity": 1.386770212175842462,
"venue": "kraken",
"output": 0,
"id": "234098b4-f55b-4ef6-9ea3-1d28010d8476",
"venueOrderId": "cd5009ad-ecd4-438a-813c-7608c85833d7",
"status": "CANCELLED",
"actualPrice": 72.050216507121990198,
"actualQuantity": 0.21356492612732545,
"createdAt": "2024-05-01T14:49:43.157286Z"
},
{
"symbol": "LTC-EUR",
"side": "BUY",
"targetPrice": 72.11,
"targetQuantity": 1.173382344115915787,
"venue": "kraken",
"output": 0,
"id": "a1c86126-9c56-407a-902c-a89a15d198f1",
"venueOrderId": "5e893e50-9260-435a-ac5c-e7430dbf34e5",
"status": "COMPLETED",
"actualPrice": 72.11,
"actualQuantity": 1.17338234,
"createdAt": "2024-05-01T14:50:44.788691Z"
}
],
"router": "VWAP",
"executor": "MAKER",
"movements": [
{
"asset": "LTC",
"amount": 1.38694726612732545
},
{
"asset": "EUR",
"amount": -99.9999997032013125489753309548559391
}
],
"createdAt": "2024-05-01T14:49:43.151138Z"
}
}
```
Below is a detailed explanation of each field in the webhook payload:
| Field | Type | Description |
| ------------------------------ | ------ | ------------------------------------------------------------------------------ |
| `event` | string | Identifies the type of event, for example, "layer1:trade:conversion:state-changed". |
| `timestamp` | string | ISO 8601 formatted date and time when the event was generated. |
| `data.id` | string | Unique identifier for the conversion event, in GUID format. |
| `data.reference` | string | A reference identifier linked to the conversion, in GUID format. |
| `data.status` | string | Current status of the conversion [PLACED, COMPLETE]. |
| `data.from.asset` | string | Type of asset being converted from. |
| `data.from.amount` | float | Amount of the asset being converted. |
| `data.to.asset` | string | Type of asset being converted to. |
| `data.to.amount` | float | Amount of the asset received after conversion. |
| `data.orders[]` | array | Array containing details of orders involved in the conversion. |
| `data.orders[].symbol` | string | Trading pair symbol. |
| `data.orders[].side` | string | Transaction side. |
| `data.orders[].targetPrice` | float | Target price for the order. |
| `data.orders[].targetQuantity` | float | Target quantity for the order. |
| `data.orders[].venue` | string | Venue where the order was placed. |
| `data.orders[].output` | int | Output index of the order (for complex trades involving multiple orders). |
| `data.orders[].id` | string | Unique identifier for the order, in GUID format. |
| `data.orders[].venueOrderId` | string | Unique identifier provided by the trading venue for the order, in GUID format. |
| `data.orders[].status` | string | Current status of the order [PLACED, COMPLETED]. |
| `data.orders[].actualPrice` | float | Actual price at which the order was executed. |
| `data.orders[].actualQuantity` | float | Actual quantity of the asset received from the order. |
| `data.orders[].createdAt` | string | ISO 8601 formatted date and time when the order was created. |
| `data.router` | string | Routing mechanism used for the conversion [VWAP]. |
| `data.executor` | string | Type of execution used for processing the conversion [MAKER]. |
| `data.movements[]` | array | Array detailing any asset movements and amounts related to the conversion. |
| `data.createdAt` | string | ISO 8601 formatted date and time when the conversion was created. |
***
### layer1:digital:balance:updated
The webhook notification sends a JSON object containing details about the event. Here is the example payloads:
```json
{
"event": "layer1:digital:balance:updated",
"timestamp": "2024-06-05T20:40:16.941138099Z",
"data": {
"reference": "First Eth Address",
"address": "0xc4b21fb39aaf5fc07d3cd4420bf3ee74a61642e9",
"asset": "ETH",
"funds": [
{
"type": "AVAILABLE",
"balance": "0.00",
"delta": "0.00"
},
{
"type": "BLOCKCHAIN",
"balance": "0.20",
"delta": "0.20"
},
{
"type": "RESERVED",
"balance": "0.20",
"delta": "0.20"
}
]
}
}
```
Below is a detailed explanation of each field in the webhook payload:
| Field | Description |
| ------------------ | ----------------------------------------------------------- |
| event | The type of event that triggered the webhook. |
| timestamp | The time when the event occurred in ISO 8601 format. |
| data | A nested object containing details of the balance update. |
| data.reference | A unique reference for the crypto address. |
| data.address | The crypto address involved in the balance update. |
| data.asset | The type of cryptocurrency asset. |
| data.funds | An array containing details of the fund balances. |
| data.funds.type | The type of fund balance [AVAILABLE, BLOCKCHAIN, RESERVED]. |
| data.funds.balance | The current balance of the fund type. |
| data.funds.delta | The change in balance for the fund type. |
---
## Approvals
Layer1 API for approvals management
Security Scheme Type:
openIdConnect
OpenID Connect URL:
https://auth.sandbox.layer1.com/auth/realms/bvnk/.well-known/openid-configuration
Security Scheme Type:
http
HTTP Authorization Scheme:
signature
Security Scheme Type:
oauth2
OAuth Flow (clientCredentials):
Token URL: https://auth.sandbox.layer1.com/auth/realms/bvnk/protocol/openid-connect/token
Scopes:
---
## Bulk vote
Submits the same vote decision (approve or reject) for multiple approval requests. Processes each request independently and returns detailed results for each operation.
Request
---
## Count approval requests
Returns the total count of approval requests matching the specified criteria.
Request
---
## Get approval request status
Retrieves the current status and last update time for a specific approval request.
Request
---
## Get approval request details
Retrieves complete details of an approval request including votes, requirements, and context.
Request
---
## Get approval settings
Retrieves current approval policy settings for a specific namespace.
Request
---
## List approval requests
Retrieves a paginated and filterable list of approval requests with support for query-based search.
Request
---
## Vote on the request a policy and context name
Holds a vote for the approval request whose context carries the values given, and applies it as soon as such a request is pending - a voter can decide before the event that raises the request arrives. The vote is recorded against the caller, so nothing of theirs is needed to apply it later.
Request
---
## Vote
Submits an approval or rejection vote for a pending approval request. Returns updated status after evaluation.
Request
---
## Create or update approval settings
Creates or updates approval policy settings for a specific namespace. Settings are validated before saving.
Request
---
## Asset Service
Layer1 API for managing asset groups and custom assets
Security Scheme Type:
openIdConnect
OpenID Connect URL:
https://auth.sandbox.layer1.com/auth/realms/bvnk/.well-known/openid-configuration
Security Scheme Type:
http
HTTP Authorization Scheme:
signature
Security Scheme Type:
oauth2
OAuth Flow (clientCredentials):
Token URL: https://auth.sandbox.layer1.com/auth/realms/bvnk/protocol/openid-connect/token
Scopes:
---
## Get single asset group by code
Retrieves a single asset group together with its enabled assets.
Request
---
## List all asset groups with enabled assets
Retrieves all asset groups together with their enabled assets for the authenticated tenant.
---
## Get single asset
Retrieves a single asset belonging to an asset group.
Request
---
## Update asset capabilities
Updates specific capabilities (`depositEnabled`, `withdrawalEnabled`, `tradeEnabled`) of a custom asset.
Request
---
## Enable or disable all assets in an asset group
Toggles the enabled state for all assets in a given asset group.
Request
---
## Enable or disable a single asset
Toggles the enabled state for a single asset in a given asset group.
Request
---
## Create or update a custom asset
Creates or updates a custom asset within an asset group.
Request
---
## Assign Contact to Merchant Channel
Assigns a contact to a merchant channel by updating the compliance data on its address. Accepts either an existing contactId, or inline complianceDetails.partyDetails which will be used to create a new internal contact hidden from the UI.
Request
---
## backfillHistoricalConversions
backfillHistoricalConversions
Request
---
## Channel
Layer1 API making management of crypto assets simple and easy
Security Scheme Type:
openIdConnect
OpenID Connect URL:
https://auth.sandbox.layer1.com/auth/realms/bvnk/.well-known/openid-configuration
Security Scheme Type:
oauth2
OAuth Flow (clientCredentials):
Token URL: https://auth.sandbox.layer1.com/auth/realms/bvnk/protocol/openid-connect/token
Scopes:
---
## Close Merchant Channel
Closes a merchant channel by its UUID.
Request
---
## Create Merchant Channel
Creates a new merchant channel with the provided details.
Request
---
## createHistoricalConversion
createHistoricalConversion
Request
---
## depositWebhookEvents
depositWebhookEvents
Request
---
## getById
getById
Request
---
## getFailedMessagesByStatus
getFailedMessagesByStatus
Request
---
## Get Merchant Channel Payment by ID
Retrieves a specific merchant channel payment by its UUID.
Request
---
## Get Merchant Channel by UUID
Retrieves a specific merchant channel by its UUID.
Request
---
## getRequiredRates
getRequiredRates
Request
---
## Get Merchant Channel Spot Rates by UUID
Retrieves spot rates for a specific merchant channel by its UUID.
Request
---
## ignore
ignore
Request
---
## List Merchant Channel Payments
Retrieves a list of merchant channel payments based on the provided filters.
Request
---
## List Merchant Channels
Retrieves a list of merchant channels based on the provided filters.
Request
---
## markAsFailed
markAsFailed
Request
---
## resendWebhook
resendWebhook
Request
---
## retry
retry
Request
---
## start
start
---
## stop
stop
---
## tradeWebhookEvents
tradeWebhookEvents
Request
---
## Update Merchant Channel
Updates the reference of a merchant channel by its UUID.
Request
---
## Add payment instrument
Adds a new payment instrument (such as a crypto address) to an existing contact. This operation may require approval depending on the trusted address approval settings.
Request
---
## Contacts
Layer1 API for managing contacts and their payment instruments
Security Scheme Type:
openIdConnect
OpenID Connect URL:
https://auth.sandbox.layer1.com/auth/realms/bvnk/.well-known/openid-configuration
Security Scheme Type:
http
HTTP Authorization Scheme:
signature
Security Scheme Type:
oauth2
OAuth Flow (implicit):
Authorization URL: https://auth.sandbox.layer1.com/auth/realms/bvnk
Scopes:
OAuth Flow (clientCredentials):
Token URL: https://auth.sandbox.layer1.com/auth/realms/bvnk/protocol/openid-connect/token
Scopes:
---
## Create contact
Creates a new contact. The contact can have multiple payment instruments such as crypto addresses.
Request
---
## Get contacts by IDs
Retrieves multiple contacts by their IDs in a single request. Specify contact IDs as a comma-separated list in the 'ids' query parameter.
Request
---
## Get contact
Retrieves a single contact by ID.
Request
---
## List payment instruments
Retrieves a paginated list of payment instruments for a specific contact.
Request
---
## List payment instruments(Contacts)
Retrieves a paginated list of payment instruments across all contacts. Supports advanced search using Lucene-like query syntax for filtering by instrument properties and associated contact information.
Request
---
## Remove payment instrument
Removes a payment instrument from a contact. This operation may require approval depending on the trusted address approval settings.
Request
---
## Update payment instrument
Updates an existing payment instrument for a contact. This operation may require approval depending on the trusted address approval settings.
Request
---
## Update contact
Updates an existing contact's information.
Request
---
## Create Merchant Channel(Crypto-payments)
Creates a new merchant channel with the provided details.
Request
---
## Crypto Payment API Endpoints
Crypto Payments API making management of crypto assets simple and easy
Hawk Payload (see: https://github.com/hueniverse/hawk)
Security Scheme Type:
apiKey
Header parameter name:
Authorization
---
## Get Merchant Channel Payment by ID(Crypto-payments)
Retrieves a specific merchant channel payment by its UUID.
Request
---
## Get Merchant Channel by UUID(Crypto-payments)
Retrieves a specific merchant channel by its UUID.
Request
---
## Get Merchant Channel Spot Rates by UUID(Crypto-payments)
Retrieves spot rates for a specific merchant channel by its UUID.
Request
---
## List Merchant Channel Payments(Crypto-payments)
Retrieves a list of merchant channel payments based on the provided filters.
Request
---
## List Merchant Channels(Crypto-payments)
Retrieves a list of merchant channels based on the provided filters.
Request
---
## Accept Payment
Accepts a pending payment with currency or payout information.
Request
---
## Create Payment
Creates a payment, either type IN or type OUT.
Request
---
## List Payments
Retrieves a list of payments on a specific Merchant ID.
Request
---
## Validate Address
Validates that a crypto address is correct.
Use this endpoint to validate that an address exists, is correctly formatted, and includes all the required data. This endpoint can help prevent your end users losing funds when submitting a payout.
Request
---
## Get Payment
Retrieves details of a specific payment using the UUID of the payment.
Request
---
## Update Payment
Updates a pending payment with currency or payout information.
Request
---
## Read smart contract state
Calls a view or pure method and returns its decoded return values. No transaction is created, no fee is incurred and no funds move.
Request
---
## Claim blockchain transaction
Claims an existing blockchain transaction.
Request
---
## Create a new address
Creates a crypto address.
Request
---
## Create addresses synchronously across all supporting networks
Creates a keypair, then concurrently creates addresses on all networks supporting the given asset or assets. Pass `asset` for one asset, `assets` for several, or `network` for one specific network. Returns once all networks complete. Retries provider calls up to 2 times with exponential backoff on transient failures (502, 503, 504).
Request
---
## Create asset pool
Creates a new asset pool, which serves as a collection of assets.
Request
---
## Query point-in-time balances
Retrieves point-in-time balance state for a bounded list of resource IDs. ADDRESS and POOL queries are supported. ADDRESS IDs that are not found or not accessible return 404. POOL IDs are tenant-filtered by the snapshot query; missing or inaccessible POOL IDs return a result entry with an empty balances array.
Request
---
## Create consolidation request
Create a new address-level consolidation request to sweep funds from a specified address to the master address.
Request
---
## Deploy a smart contract
Deploys a registered smart contract artifact from an address in the given asset pool. The deployment is processed asynchronously; poll the returned id for its status and, once confirmed, the address the contract was created at.
Request
---
## Invoke a smart contract method
Invokes a state-changing method on a smart contract from an address in the given asset pool. Read-only methods must be queried through contract calls instead. Processed asynchronously.
Request
---
## Create an export job
Starts a CSV export. Use the required query parameter 'type' to select either a transaction or balance export.
Request
---
## Create key pair
Creates a new key pair from which different addresses can be created
Request
---
## Create return transaction request
Create a new blockchain transaction meant to return previously deposited funds
Request
---
## Create staking request
Create a new blockchain staking transaction
Request
---
## Create transaction request
Creates a blockchain transaction.
Request
---
## Delete asset pool
Deletes an asset pool with given ID if it doesn't have created addresses or transactions.
Request
---
## Digital Asset
Layer1 API making management of crypto assets simple and easy
Security Scheme Type:
openIdConnect
OpenID Connect URL:
https://auth.sandbox.layer1.com/auth/realms/bvnk/.well-known/openid-configuration
Security Scheme Type:
http
HTTP Authorization Scheme:
signature
Security Scheme Type:
oauth2
OAuth Flow (clientCredentials):
Token URL: https://auth.sandbox.layer1.com/auth/realms/bvnk/protocol/openid-connect/token
Scopes:
---
## Estimate transaction fee
Estimate fee to send blockchain transaction.
Request
---
## Execute an action on an address
Executes an action on a crypto address. Supported actions: CREATE_TAG — creates a new destination tag on a tag-based network address (e.g. Ripple/XRP); UPDATE_COMPLIANCE_DATA — updates originator and beneficiary identity on an address key pair; UPDATE_TAG_COMPLIANCE_DATA — updates originator and beneficiary identity on a specific address tag; returns 404 if the tag does not exist.
Request
---
## Execute an action on a transaction request
Executes an action on a transaction request. Supported actions: REJECT — rejects a transaction request that is awaiting funds, before it reaches the blockchain. Only requests in FUNDS_NEEDED or FUNDS_REQUESTED are rejectable; any other status is refused, including a request awaiting an approval decision (reject it through that approval) and one already published or in a final status. Repeating the action on an already rejected request succeeds without further effect. No Idempotency-Key is required: the action is idempotent on the state of the request itself, not on a client-supplied key.
Request
---
## Get point-in-time address balance
Retrieves balance state of an address at a specific timestamp
Request
---
## Get address
Retrieves details of the specific address.
Request
---
## Get asset pool balances at point in time
Retrieves the aggregated balance state of an asset pool at a specific historical point in time.
Request
---
## Get max asset pool withdrawable balance
Retrieves the maximum approximate withdrawable amount for a specified network and asset. **Note**: Fees are not included in this calculation.
Request
---
## Get asset pool summary
Retrieve authenticated summary across all asset pools.
---
## Get asset pool
Retrieves a specific asset pool by its unique ID.
Request
---
## Get a contract deployment
Retrieves a deployment, including its status and — once confirmed — the address the contract was created at.
Request
---
## Get a contract invocation
Retrieves an invocation, including its status and, once broadcast, its transaction hash.
Request
---
## Get smart contract artifact
Retrieves a smart contract artifact by id, if it is visible to the caller's tenant (owned by that tenant or available to every tenant). Bytecode is omitted.
Request
---
## Get export with a download link
Retrieves export details, including a short-lived download link, using the export ID.
Request
---
## List all exports
Retrieves a paginated list of exports based on type and pagination settings.
Request
---
## Get key pair
Retrieve a key pair given id
Request
---
## Get all networks
Get all supported networks and their assets. By default, only enabled networks are returned.
Request
---
## Get transaction
Retrieves a transaction by its ID.
Request
---
## Get transaction request
Retrieves a transaction request by its ID.
Request
---
## List addresses
Retrieves a cursor-paginated list of crypto addresses. Follow `nextCursor` from each response to page forward; the last page omits it and reports `hasNext: false`. Results are ordered newest-first by creation time.
Request
---
## List asset pools
Retrieves a paginated list of asset pools.
Request
---
## List smart contract artifacts
Retrieves a paginated list of smart contract artifacts visible to the caller's tenant (its own artifacts plus artifacts available to every tenant). Bytecode is omitted.
Request
---
## List deposits requiring remediation
Retrieves a paginated list of deposit transactions for the authenticated tenant that require remediation (e.g. missing compliance details).
Request
---
## List key pairs
Retrieve paginated list of key-pairs belonging to a asset pool
Request
---
## List screening results
Retrieves a paginated list of the most recent transfers along with their screening results.
Request
---
## List address tags
Retrieves a paginated list of address tags.
Request
---
## List transaction requests
Retrieves a paginated list of transaction requests.
Request
---
## List transactions
Retrieves a paginated list of transactions.
Request
---
## Approve, reject, bypass, or remediate screening of a transfer
Approves or rejects a held transfer, bypasses screening for a pending one, or remediates a transfer. The required scope depends on the requested action type.
Request
---
## Patch address
Partially updates an address using JSON Merge Patch (RFC 7396). Supports patching `metadata`. Within metadata, keys with non-null values are set, keys with null values are removed, absent keys are unchanged.
Request
---
## Patch key pair
Partially updates a key pair using JSON Merge Patch (RFC 7396). Only the `metadata` field can be patched. Within metadata, keys with non-null values are set, keys with null values are removed, absent keys are unchanged.
Request
---
## Update asset pool
Updates a specific asset pool property by its ID.
Request
---
## Validate address(Digital-assets)
Validate a blockchain address.
Request
---
## Cancel conversion
Attempt to cancel an in-progress conversion. Only conversions with status PENDING or PROCESSING can be cancelled.
Request
---
## Create conversion export
Initiates an asynchronous job to export conversion data for the specified date range. The export will be generated in the requested format and uploaded to S3. The job processes conversions in batches and includes conversion ID, source/destination assets, estimated input/output amounts, and other relevant metadata.
Request
---
## Create conversion
Creates a new conversion request to exchange one asset for another. The conversion will be processed asynchronously.
Request
---
## Create a venue deposit address directly
Creates a new deposit address by directly providing all required details. This is a synchronous operation that stores the address immediately without venue interaction. Use this when you already have the deposit address details from an external source.
Request
---
## Delete a venue deposit address
Deletes an existing deposit address for a specific venue, asset, network, and address combination. This operation is idempotent - deleting a non-existent address will return success.
Request
---
## Estimate conversion
Get an accurate estimate of the conversion result including expected output amount, fees, and execution time
Request
---
## List possible destination assets
Retrieve a list of assets that can be converted to from the specified source asset, considering available trading symbols
Request
---
## Get all indicative exchange rates
Retrieve all available indicative exchange rates for a given base currency. This endpoint returns exchange rates from the specified base asset to all other supported assets. The rates are calculated using mid-market/ticker data and provide estimates for conversion purposes.
Request
---
## Get venue balance summary aggregated by asset across available venues
Get venue balance summary
Request
---
## Get conversion
Retrieve detailed information about a specific conversion using its unique identifier
Request
---
## Get export with a download link(Trade)
Retrieves export details, including a short-lived download link, using the export ID.
Request
---
## List all exports(Trade)
Retrieves a paginated list of exports based on type and pagination settings.
Request
---
## Get indicative exchange rate
Retrieve an indicative exchange rate between two assets using mid-market/ticker rates. The rate is calculated based on current market data and provides an estimate for conversion purposes. If no amount is specified, the rate is calculated for 1 unit of the source asset.
Request
---
## Get aggregated order book
Retrieve an aggregated order book that combines data from multiple venues for a given trading symbol. The aggregation includes venue information and can apply different fee strategies and order book types.
Request
---
## Get market data order books
Retrieve a list of order books for a given trading symbol. Order books contain bid and ask orders with price levels and quantities. If a venue is specified, only order books from that venue are returned.
Request
---
## Get market data tickers
Retrieve a list of tickers for a given trading symbol. Tickers contain current market data including last price, bid/ask prices, volume, and high/low prices. If a venue is specified, only tickers from that venue are returned.
Request
---
## Get live venue quotes
Fetch live quotes from a quote-executing venue. Unrecognized query parameters are relayed to the venue as venue-specific options.
Request
---
## Get venue deposit
Retrieve a single venue deposit
Request
---
## Get venue withdrawal
Retrieve detailed information about a specific withdrawal using its unique identifier
Request
---
## List venue balances
List venue balances
Request
---
## List conversions
Retrieve a paginated list of conversions with optional filtering using Lucene query syntax
Request
---
## List venue deposit addresses
Retrieve a paginated list of deposit addresses across all venues with optional filtering by venue, asset, status, and other criteria
Request
---
## List venue deposits
Retrieve paginated list of venue deposits
Request
---
## List venue withdrawals
Retrieve a paginated list of venue withdrawals with optional filtering by asset and venue
Request
---
## List venues
Retrieve a list of trading venues
Request
---
## Request a new venue deposit address
Creates a request for a new deposit address at a specific venue for the given asset. The address generation is processed asynchronously and the address will be available once generated.
Request
---
## Request venue withdrawal
Creates a new withdrawal request to transfer funds from a venue to a blockchain address or fiat destination. The withdrawal will be processed asynchronously and may require approval depending on configuration.
Request
---
## Trading(Trade)
Layer1 API making management of crypto assets simple and easy
Security Scheme Type:
openIdConnect
OpenID Connect URL:
https://auth.sandbox.layer1.com/auth/realms/bvnk/.well-known/openid-configuration
Security Scheme Type:
http
HTTP Authorization Scheme:
signature
---
## Update venue settings
Update venue properties like trading enabled status
Request
---
## Create managed balance with optional eligible sources
Create a new managed balance with the ability to provide eligible sources upfront
Request
---
## Remove managed balance
Delete a managed balance by ID. Allowed only if balanced is not referenced anywhere as a source.
Request
---
## Delete cached processing profile
Remove the cached processing profile mapping for a specific managed balance
Request
---
## Trigger a manual top-up
Execute a balance adjustment for a managed balance
Request
---
## Trigger a manual top-up(Treasury)
Execute a balance adjustment for a managed balance
Request
---
## Get activity logs for a managed balance
Retrieve a paginated list of activity logs for a specific managed balance within a date range. The date range cannot exceed 3 months. Activity logs include notifications and treasury operation status updates.
Request
---
## List managed balances
Retrieve a paginated list of all managed balances
Request
---
## Get current balance
Retrieve the current balance for a specific managed balance
Request
---
## Get eligible sources for a managed balance
Retrieve all eligible sources with their details and priorities for a managed balance
Request
---
## Get linked managed balances
Retrieve a list of managed balances that are linked to the specified managed balance
Request
---
## Get revisions for a managed balance
Retrieve a paginated list of revisions for a specific managed balance by ID
Request
---
## Get managed balance
Retrieve a specific managed balance by ID
Request
---
## Get processing profile
Retrieve the processing profile mapped to a specific managed balance
Request
---
## Get treasury operations
Retrieve a paginated list of treasury operations for the authenticated tenant
Request
---
## Update managed balance property
Update a specific property (monitoring-enabled or forecast-enabled) for a managed balance
Request
---
## Update threshold for a managed balance
Update thresholds for a managed balance
Request
---
## Remove funding source for a managed balance
Remove an eligible source from a managed balance
Request
---
## Get templates to create a managed balance
Retrieve templates for managed balance creation for the authenticated tenant
Request
---
## Treasury
Layer1 API for treasury management
Security Scheme Type:
openIdConnect
OpenID Connect URL:
https://auth.sandbox.layer1.com/auth/realms/bvnk/.well-known/openid-configuration
Security Scheme Type:
http
HTTP Authorization Scheme:
signature
Security Scheme Type:
oauth2
OAuth Flow (clientCredentials):
Token URL: https://auth.sandbox.layer1.com/auth/realms/bvnk/protocol/openid-connect/token
Scopes:
---
## Add funding source for a managed balance
Add or update an eligible source for a managed balance
Request
---
## Update managed balance
Create a new managed balance or update an existing one
Request
---
## validate
validate
Request
---
## Approval request status changed
Triggered when the status of an approval request changes to a final state.
This webhook informs about all completed requests: approved, rejected, and expired.
It is not sent when a request is created or pending.
Request
---
## Approval request vote required
Triggered when an approval request requires a user's vote.
This webhook is pushed only if the request waits for a user's vote.
It won't be received if an approval request is auto-approved or auto-rejected.
Request
---
## API audit event
This webhook is sent for necessary, predefined API calls configured on the API gateway.
It serves as a common audit event that triggers for every authenticated action performed on the APIs,
covering platform operations such as creating transactions, creating wallets, and similar actions.
All actions captured by this webhook are executed by an authenticated party.
Request
---
## Authentication login audit event
This webhook captures user and client login events recorded by Keycloak.
To enhance the audit trail with comprehensive login information, Layer1 leverages Keycloak
as an authentication provider, which exposes its own set of events. A custom event listener
implemented within Keycloak wraps these events into the layer1:audit:auth:login webhook format,
providing a unified audit log for all authentication activities.
Request
---
## Cryptocurrency address created
Triggered when a new cryptocurrency address is created.
This webhook provides details about the newly created address including its network and associated key pair.
Request
---
## Consolidation status change
Triggered when the status of a cryptocurrency consolidation changes.
This webhook provides real-time updates on consolidation processing status through various stages:
CREATED, SIGNED, DETECTED, UNCONFIRMED, CONFIRMED, and SUCCESS.
Request
---
## Deposit status change
Triggered when the status of a cryptocurrency deposit changes.
This webhook provides real-time updates on deposit processing status through various stages:
DETECTED, UNCONFIRMED, CONFIRMED, SCREENING_REQUESTED, and SUCCESS.
Request
---
## Withdrawal status change
Triggered when the status of a cryptocurrency withdrawal changes.
This webhook provides real-time updates on withdrawal processing status through various stages:
CREATED, SIGNED, UNCONFIRMED, and CONFIRMED.
Request
---
## Digital balance updated
Triggered when a digital asset balance is updated.
This webhook provides details about balance changes including available, blockchain, and reserved fund types.
Request
---
## Layer1 Webhooks API
Layer1 Webhooks API provides real-time notifications for various events across the Layer1 platform.
This API specification describes the webhook events that Layer1 sends to your configured endpoints.
## Webhook Categories
- **Crypto Webhooks**: Address creation and transaction lifecycle events
- **Deposit Webhooks**: Deposit status change events
- **Withdrawal Webhooks**: Withdrawal status change events
- **Consolidation Webhooks**: Asset consolidation events
- **Trade Webhooks**: Conversion and trading events
- **Balance Webhooks**: Balance update events
- **Approval Webhooks**: Approval request and voting events
- **Audit Webhooks**: API and authentication audit events
---
## Trade conversion created
Triggered when a new trade conversion is created.
This webhook provides details about the conversion including the assets being converted and the routing mechanism used.
Request
---
## Trade conversion state change
Triggered when the state of a trade conversion changes.
This webhook provides real-time updates on conversion processing status including order details and movements.
Request
---
## Create a new destination
Allows admin to create a new destination for a provided account
Request
---
## Create a new destination(Webhooks)
Creates a new destination
Request
---
## Hook
Layer1 API making management of crypto assets simple and easy
Security Scheme Type:
openIdConnect
OpenID Connect URL:
https://auth.sandbox.layer1.com/auth/realms/bvnk/.well-known/openid-configuration
Security Scheme Type:
http
HTTP Authorization Scheme:
signature
Security Scheme Type:
oauth2
OAuth Flow (clientCredentials):
Token URL: https://auth.sandbox.layer1.com/auth/realms/bvnk/protocol/openid-connect/token
Scopes:
---
## List destinations for a specific account
Allows admin to retrieve paginated list of destinations for a specific account
Request
---
## List destinations
Retrieve paginated list of destinations
Request
---
## List events
Retrieve paginated list of events
Request
---
## Remove an existing destination
Removes an existing destination
Request
---
## Remove a destination
Allows admin to permanently remove a destination for a provided account
Request
---
## Replay events
Replay completed events by re-publishing them.
Request
---
## Update a new destination
Allows admin to update certain details of a destination for a provided account
Request
---
## Update the existing destination
Updates the existing destination
Request
---
## Layer 1 documentation
export default function Layer1IndexRedirect() {
return ;
}