Account Owner Verification Automation
When a user deposits digital assets from other exchange (VASP, Virtual Asset Service Provider) into Upbit, the VASP is required to verify that the depositor and the account owner are the same person to prevent money laundering. This section explains how to use the Upbit API to automate the account owner verification process.
The full code examples can be found in the Recipes menu.
Click the button above to navigate to the full code Recipe page of this tutorial.
Get Started
Digital asset deposits follow these steps:
- Generate a deposit address in Upbit to receive digital assets.
- Transfer digital assets from other exchange to the generated Upbit deposit address.
- Verify that the sender and receiver account owner match.
- After verification, confirm that the deposit has been credited.
This guide explains how to implement these steps using the Upbit API and how to automate Travel Rule verification when depositing from other exchange into Upbit.
Verifies whether the sender and recipient information match, and only deposits that pass this verification are successfully processed. For more details about the Travel Rule, please see the link below: Upbit Help Center > Learn about the Travel Rule
Learn about the Travel Rule
- Singapore (sg): https://sg-api.upbit.com
Authentication Guide
The Deposit Management API is part of the Exchange API and requires authentication. Please refer to the Authentication documentation and the recipe below to ensure that the authentication header is included in all API calls.
Deposit Address Creation
To deposit digital assets from other exchange into Upbit, you first need a deposit address. Implement a function that generates a deposit address for the specified digital asset by calling the Create Deposit Address API.
def create_deposit_address(currency: str, net_type: str) -> Mapping:
body = {
"currency": currency,
"net_type": net_type
}
query_string = _build_query_string(body)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://sg-api.upbit.com/v1/deposits/generate_coin_address"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.post(url, headers=headers, json=body).json()
return response
Since this API is processed asynchronously, the initial request may return a response indicating "creating" or "in progress."
{
"success": true,
"message": "Generating a BTC deposit address."
}
After a certain time, if you call the function again or if a deposit address already exists, the generated deposit address will be returned as shown below.
{
"currency": "BTC",
"net_type": "BTC",
"deposit_address": "<Deposit_Address>",
"secondary_address": null
}
Deposit from Other Exchanges to Upbit
Deposit digital assets from other exchange into your Upbit deposit address..
Implement a function that calls the Get Deposit API to retrieve information about a specific deposit. When transferring digital assets from other exchange to Upbit, you can check the deposit status using the UUID or TxID provided.
- Get Deposit by UUID
def get_deposit_by_uuid(uuid: str) -> Mapping:
params = {
"uuid": uuid
}
query_string = _build_query_string(params)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://sg-api.upbit.com/v1/deposit"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers, params=params).json()
return response
- Get Deposit by TxID
def get_deposit_by_txid(txid: str) -> Mapping:
params = {
"txid": txid
}
query_string = _build_query_string(params)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://sg-api.upbit.com/v1/deposit"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers, params=params).json()
return response
If the state
field of the deposit information is TRAVEL_RULE_SUSPECTED
, it indicates that Travel Rule account owner verification is required.
{
"type": "deposit",
"uuid": "<your_deposit_uuid>",
"currency": "BTC",
"net_type": "BTC",
"txid": "<your_deposit_txid>",
"state": "TRAVEL_RULE_SUSPECTED",
"created_at": "2025-07-04T15:00:00",
"done_at": "2025-07-04T15:00:10",
"amount": "100000.0",
"fee": "0.0",
"transaction_type": "default"
}
Travel Rule Verification
List Travel Rule Supporting VASPs
To request a Travel Rule account holder verification, you need the UUID of the counterparty exchange. Implement a function that calls the List Travel Rule Supporting VASPs API, then searches by exchange name and returns the corresponding UUID.
def get_vasp_uuid(vasp_name: str) -> str:
params = {
"vasp_name": vasp_name
}
query_string = _build_query_string(params)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://sg-api.upbit.com/v1/travel_rule/vasps"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers, params=params).json()
vasp_uuid = next((item.get('vasp_uuid') for item in response if item.get('vasp_name') == vasp_name), None)
if vasp_uuid is None:
raise ValueError("{vasp_name} is NOT_FOUND".format(vasp_name=vasp_name))
return vasp_uuid
Account Owner Verification Request
Implement a function that performs Travel Rule verification using the UUID of the depositing exchange together with either the deposit UUID or the TxID.
- Travel Rule Verification by UUID - Verify Travel Rule by Deposit UUID
def verify_travel_rule_by_uuid(deposit_uuid: str, vasp_uuid: str) -> str:
params = {
"deposit_uuid": deposit_uuid,
"vasp_uuid": vasp_uuid
}
query_string = _build_query_string(params)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://sg-api.upbit.com/v1/travel_rule/deposit/uuid"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.post(url, headers=headers, json=params).json()
verification_result = response.get('verification_result')
if verification_result is None:
raise ValueError("Please check the response. {response}".format(response=response))
else:
return verification_result
Travel Rule Verification by TxID - Verify Travel Rule by Deposit TxID
def verify_travel_rule_by_txid(deposit_txid: str, vasp_uuid: str, currency: str, net_type: str) -> str:
params = {
"txid": deposit_txid,
"vasp_uuid": vasp_uuid,
"currency": currency,
"net_type": net_type
}
query_string = _build_query_string(params)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://sg-api.upbit.com/v1/travel_rule/deposit/txid"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.post(url, headers=headers, json=params).json()
verification_result = response.get('verification_result')
if verification_result is None:
raise ValueError("Please check the response. {response}".format(response=response))
else:
return verification_result
When the function is executed, you can check the verification result as shown below.
{
"deposit_uuid": "00000000-0000-0000-0000-000000000000",
"verification_result": "verified",
"deposit_state": "PROCESSING"
}
Confirm Deposit Reflection
After completing the Travel Rule verification, use the previously implemented deposit information query function to check the status of the deposit.
{
"type": "deposit",
"uuid": "<your deposit uuid>",
"currency": "BTC",
"net_type": "BTC",
"txid": "<your deposit transaction txid>",
"state": "ACCEPTED",
"created_at": "2025-07-04T15:00:02+09:00",
"done_at": "2025-07-04T15:00:48+09:00",
"amount": "100000.0",
"fee": "0.0",
"transaction_type": "default"
}
Full Code Example
The complete code example for automatic account owner verification is as follows..
from urllib.parse import unquote, urlencode
from typing import Any
from collections.abc import Mapping
import hashlib
import uuid
import jwt # PyJWT
import requests
def create_deposit_address(currency: str, net_type: str) -> Mapping:
body = {
"currency": currency,
"net_type": net_type
}
query_string = _build_query_string(body)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://sg-api.upbit.com/v1/deposits/generate_coin_address"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.post(url, headers=headers, json=body).json()
return response
def get_deposit_by_uuid(uuid: str) -> Mapping:
params = {
"uuid": uuid
}
query_string = _build_query_string(params)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://sg-api.upbit.com/v1/deposit"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers, params=params).json()
return response
def get_deposit_by_txid(txid: str) -> Mapping:
params = {
"txid": txid
}
query_string = _build_query_string(params)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://sg-api.upbit.com/v1/deposit"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers, params=params).json()
return response
def get_vasp_uuid(vasp_name: str) -> str:
params = {
"vasp_name": vasp_name
}
query_string = _build_query_string(params)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://sg-api.upbit.com/v1/travel_rule/vasps"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers, params=params).json()
vasp_uuid = next((item.get('vasp_uuid') for item in response if item.get('vasp_name') == vasp_name), None)
if vasp_uuid is None:
raise ValueError("{vasp_name} is NOT_FOUND".format(vasp_name=vasp_name))
return vasp_uuid
def verify_travel_rule_by_uuid(deposit_uuid: str, vasp_uuid: str) -> str:
params = {
"deposit_uuid": deposit_uuid,
"vasp_uuid": vasp_uuid
}
query_string = _build_query_string(params)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://sg-api.upbit.com/v1/travel_rule/deposit/uuid"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.post(url, headers=headers, json=params).json()
verification_result = response.get('verification_result')
if verification_result is None:
raise ValueError("Please check the response. {response}".format(response=response))
else:
return verification_result
def verify_travel_rule_by_txid(deposit_txid: str, vasp_uuid: str, currency: str, net_type: str) -> str:
params = {
"txid": deposit_txid,
"vasp_uuid": vasp_uuid,
"currency": currency,
"net_type": net_type
}
query_string = _build_query_string(params)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://sg-api.upbit.com/v1/travel_rule/deposit/txid"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.post(url, headers=headers, json=params).json()
verification_result = response.get('verification_result')
if verification_result is None:
raise ValueError("Please check the response. {response}".format(response=response))
else:
return verification_result
# Uncomment and run this code to execute the Travel Rule verification process. Please double-check before running.
# if __name__ == "__main__":
# ####### Travel Rule Verification by UUID ########
# currency = "<Enter_your_currency>"
# net_type = "<Enter_your_net_type>"
# deposit_address_dict = create_deposit_address(currency=currency, net_type=net_type)
# deposit_address = deposit_address_dict.get('deposit_address')
# if deposit_address is not None:
# deposit_uuid = "<Enter_your_deposit_uuid>"
# deposit_state = get_deposit_by_uuid(deposit_uuid)['state']
# print(deposit_state)
# if deposit_state == "ACCEPTED":
# vasp_uuid = get_vasp_uuid("Binance")
# result = verify_travel_rule_by_uuid(deposit_uuid=deposit_uuid, vasp_uuid=vasp_uuid)
# if result == "verified":
# print(get_deposit_by_uuid(deposit_uuid))
# else:
# raise ValueError("Check the travel rule verification result.")
# else:
# raise ValueError("This deposit does not require verification.")
# else:
# raise ValueError("Check the deposit address.")
# ######## Travel Rule Verification by TxID ########
# currency = "<Enter_your_currency>"
# net_type = "<Enter_your_net_type>"
# deposit_address_dict = create_deposit_address(currency=currency, net_type=net_type)
# deposit_address = deposit_address_dict.get('deposit_address')
# if deposit_address is not None:
# deposit_txid = "<Enter_your_deposit_txid>"
# deposit_state = get_deposit_by_txid(deposit_txid)['state']
# if deposit_state == "TRAVEL_RULE_SUSPECTED":
# vasp_uuid = get_vasp_uuid("Binance")
# result = verify_travel_rule_by_txid(deposit_txid=deposit_txid, vasp_uuid=vasp_uuid, currency=currency, net_type=net_type)
# if result == "verified":
# print(get_deposit_by_txid(deposit_txid))
# else:
# raise ValueError("Check the travel rule verification result.")
# else:
# raise ValueError("This deposit does not require verification.")
# else:
# raise ValueError("Check the deposit address.")
Updated 5 days ago