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.

Tutorial Example Code Guide
This tutorial includes step-by-step partial code and API call/response examples to aid user understanding.
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:

  1. Generate a deposit address in Upbit to receive digital assets.
  2. Transfer digital assets from other exchange to the generated Upbit deposit address.
  3. Verify that the sender and receiver account owner match.
  4. 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.

This guide includes the digital asset deposit process using real assets.
Therefore, if you write and execute the code exactly as shown in this guide, your real assets held in other exchange will be deposited into Upbit. If you do not wish to deposit digital assets, please refer only to the Upbit API usage instructions and make sure not to call any functions related to actual deposits.
What is the Travel Rule?
The Travel Rule is a regulation introduced to prevent money laundering (AML). It requires Virtual Asset Service Providers (VASPs) that send or receive digital assets to verify the information of both the sender and the recipient before processing a transfer.

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
The Travel Rule verification feature is supported only in Singapore
The examples on this page use Singapore fiat code (SGD). Please note that this feature is only supported in Singapore. For other regions (id, th), this feature is not available.

- 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..

Please Confirm Before Deposit!
If the blockchain network of the digital asset does not match, or if the generated deposit address differs from the actual address entered for receiving the asset, the deposit may not be processed. Always verify both the deposit address and the asset’s blockchain network before making a deposit.

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.

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
Travel Rule verification requests for the same deposit are limited to once every 10 minutes.
Therefore, be sure to review the input information before making a request.

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.")