HTX(후오비) API 서명 오류 5가지 — api-signature-not-valid부터 첫 주문까지
api-signature-not-valid가 뜨는 이유는 거의 서명 대상 문자열을 잘못 만든 것 하나입니다. HTX는 ① HTTP 메서드 ② 호스트(api.huobi.pro) ③ 경로 ④ ASCII 오름차순으로 정렬한 쿼리스트링을 줄바꿈으로 이어 붙인 4줄 문자열에 HmacSHA256을 적용하고, 결과를 hex가 아니라 Base64로 인코딩합니다. 여기에 타임스탬프가 밀리초가 아니라 UTC 날짜·시간 문자열이라는 점까지 맞추면 대부분 해결됩니다.
바이낸스나 Bybit API를 먼저 만져본 분일수록 HTX에서 오래 막힙니다. 바이낸스는 쿼리스트링에 hexdigest를 붙이고 타임스탬프도 밀리초 정수인데, HTX는 규칙이 셋 다 다릅니다. 그래서 "코드는 똑같이 짰는데 401만 돌아온다"는 상황이 생깁니다.
이 글은 HTX(후오비) 현물 REST API에서 서명을 만들고, account-id를 조회하고, 첫 주문을 넣는 데까지의 실제 코드와 실제 응답을 정리한 것입니다. 코인 자동매매 전체 그림은 코인 자동매매 완전 가이드에서 다뤘고, 여기서는 HTX 서명 한 가지만 파고듭니다.
먼저 밝힙니다. 아래 엔드포인트·파라미터·오류 코드 문자열은 2026년 8월 시점의 공개 문서를 근거로 정리했습니다. 거래소 API 스펙은 예고 없이 바뀌므로 실제 구현 전 HTX 공식 API 문서에서 현재 스펙을 반드시 확인하세요. 또한 이 글은 기술 문서이며 특정 거래소 이용이나 투자를 권유하지 않습니다.
이 글에서 해결하는 것
- HTX API 3분 구조 — 도메인과 인증 파라미터
- 서명 4줄 규칙 — 실제 pre-signed text
- 파이썬 서명 구현 (복붙 가능)
- 오류 5가지 — 증상 · 원인 · 해결
- account-id 없이는 주문이 안 나간다
- 첫 주문 — POST /v1/order/orders/place
- ccxt로 우회하는 선택지
- 봇으로 굴리기 전 확인할 것
1. HTX API 3분 구조 — 도메인과 인증 파라미터
HTX는 2023년 Huobi Global에서 이름을 바꿨지만, 현물 API 도메인은 여전히 api.huobi.pro를 씁니다. 그래서 문서와 코드에 huobi와 htx가 섞여 나옵니다. 검색할 때도 두 이름을 다 넣어야 자료가 나옵니다.
| 구분 | 값 | 메모 |
|---|---|---|
| REST 호스트 | api.huobi.pro | 현물 기준. 서명 2번째 줄에 호스트만 들어간다 |
| 공개 시세 | GET /market/tickers | 서명 불필요 |
| 캔들 | GET /market/history/kline | symbol, period, size |
| 계정 목록 | GET /v1/account/accounts | 서명 필요. account-id를 여기서 얻는다 |
| 현물 주문 | POST /v1/order/orders/place | 서명은 쿼리스트링만, 주문 내용은 body(JSON) |
| WebSocket | wss://api.huobi.pro/ws | 시세용. 메시지가 GZIP 압축으로 온다 |
인증이 필요한 요청에는 아래 4개 파라미터가 쿼리스트링으로 항상 붙습니다. 헤더가 아니라 쿼리라는 점이 바이낸스와 다릅니다.
AccessKeyId = 발급받은 Access Key
SignatureMethod = HmacSHA256 (문자열 고정)
SignatureVersion = 2 (문자열 고정)
Timestamp = 2026-08-01T03:14:05 ← UTC, 초 단위까지
2. 서명 4줄 규칙 — 실제 pre-signed text
HTX 서명의 핵심은 무엇에 서명하는가입니다. 아래처럼 줄바꿈(\n)으로 4줄을 만들고, 이 문자열 전체에 Secret Key로 HmacSHA256을 겁니다.
GET
api.huobi.pro
/v1/account/accounts
AccessKeyId=abcd-1234&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2026-08-01T03%3A14%3A05
여기서 사람들이 실제로 틀리는 지점은 정확히 네 군데입니다.
- 1줄: 메서드는 대문자 —
GET,POST. 소문자면 실패합니다. - 2줄: 프로토콜을 빼고 호스트만 —
https://api.huobi.pro가 아니라api.huobi.pro입니다. - 4줄: 파라미터는 ASCII 오름차순 정렬 —
AccessKeyId→SignatureMethod→SignatureVersion→Timestamp순. 대문자가 소문자보다 앞이라는 것도 영향을 줍니다. - 4줄: 값은 URL 인코딩된 상태로 — 타임스탬프의 콜론(
:)이%3A로 바뀐 문자열에 서명해야 합니다.
3. 파이썬 서명 구현 (복붙 가능)
표준 라이브러리만으로 됩니다. requests와 hmac, hashlib, base64면 충분합니다.
import base64, hashlib, hmac, datetime, urllib.parse
import requests, json
ACCESS_KEY = "YOUR_ACCESS_KEY" # 코드에 직접 쓰지 말고 환경변수로
SECRET_KEY = "YOUR_SECRET_KEY"
HOST = "api.huobi.pro"
def signed_params(method: str, path: str, extra: dict | None = None) -> dict:
params = {
"AccessKeyId": ACCESS_KEY,
"SignatureMethod": "HmacSHA256",
"SignatureVersion": "2",
# 밀리초가 아니라 UTC 날짜·시간 문자열
"Timestamp": datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S"),
}
if extra:
params.update(extra)
# ASCII 오름차순 정렬 후 URL 인코딩 — 이 순서가 서명 대상이다
qs = urllib.parse.urlencode(sorted(params.items()))
payload = "\n".join([method.upper(), HOST, path, qs])
digest = hmac.new(SECRET_KEY.encode(), payload.encode(),
hashlib.sha256).digest()
# hexdigest()가 아니라 base64 — 가장 흔한 실수
params["Signature"] = base64.b64encode(digest).decode()
return params
r = requests.get(f"https://{HOST}/v1/account/accounts",
params=signed_params("GET", "/v1/account/accounts"),
timeout=10)
print(json.dumps(r.json(), indent=2, ensure_ascii=False))
성공하면 이런 응답이 옵니다. status가 ok인지부터 봅니다.
{
"status": "ok",
"data": [
{ "id": 1234567, "type": "spot", "subtype": "", "state": "working" },
{ "id": 1234568, "type": "margin", "subtype": "btcusdt", "state": "working" }
]
}
4. 오류 5가지 — 증상 · 원인 · 해결
1api-signature-not-valid — 타임스탬프를 KST로 만들었다
{
"status": "error",
"err-code": "api-signature-not-valid",
"err-msg": "Signature not valid: Verification failure [_]",
"data": null
}
datetime.datetime.now()를 쓰면 한국 시간이 들어가 9시간이 어긋납니다. 반드시 utcnow()(또는 datetime.now(datetime.UTC))를 쓰세요. VPS에서 돌린다면 서버 시계 자체가 틀어진 경우도 같은 증상이라 NTP 동기화를 확인합니다.
timedatectl set-ntp true
timedatectl status | grep "System clock synchronized"
2api-signature-not-valid — hexdigest를 썼다
바이낸스 코드를 그대로 옮겨오면 십중팔구 여깁니다. 바이낸스는 hexdigest() 결과를 쿼리에 붙이지만 HTX는 digest()를 Base64로 인코딩합니다. 같은 오류 코드가 뜨기 때문에 원인을 찾기 어렵습니다. 발급 절차 자체가 처음이라면 형식은 다르지만 흐름이 비슷한 바이낸스 API 발급 가이드를 옆에 두고 비교해 보면 차이가 명확히 보입니다.
3api-signature-not-valid — 서명한 파라미터와 보낸 파라미터가 다르다
가장 잡기 어려운 경우입니다. 서명은 파라미터 4개로 만들어 놓고, 실제 요청에는 symbol=btcusdt를 하나 더 얹는 식이죠. 쿼리스트링에 들어가는 모든 파라미터가 서명 대상에 포함되어야 합니다. 위 코드에서 extra를 params에 먼저 합친 뒤 정렬하는 이유가 이것입니다.
단, POST 주문은 반대입니다. POST /v1/order/orders/place에서 주문 내용(symbol·amount·type)은 서명에 넣지 않습니다. 서명 대상은 인증 파라미터 4개뿐이고, 주문 내용은 JSON body로 따로 보냅니다. 이 규칙을 뒤집으면 3번과 똑같은 오류가 납니다.
4invalid-timestamp — 요청이 너무 늦게 도착했다
{
"status": "error",
"err-code": "invalid-timestamp",
"err-msg": "Invalid timestamp",
"data": null
}
서명은 맞는데 만든 시각과 도착한 시각의 차이가 허용 범위를 넘은 경우입니다. 타임스탬프를 프로그램 시작 시점에 한 번 만들어 재사용하면 이 오류가 납니다. 요청마다 새로 생성하세요. 네트워크가 느린 해외 VPS에서도 발생할 수 있어, 재시도 로직에서는 타임스탬프를 반드시 다시 만들어야 합니다.
5호출이 잦아 차단됐다 — 429와 IP 제한
서명과 무관하게, 시세를 빠른 루프로 계속 긁으면 요청이 거부되기 시작합니다. 해결은 두 가지입니다. ① 시세는 REST API 폴링 대신 WebSocket 구독으로 바꾼다(wss://api.huobi.pro/ws, 메시지가 GZIP으로 오므로 gzip.decompress()가 필요합니다). ② 주문·계정 조회에는 토큰 버킷 방식의 레이트리미터를 건다. 거래소마다 한도가 다르므로 설계 원칙은 API 호출 제한(레이트리밋) 설계에 정리해 뒀습니다.
한도 수치는 적지 않겠습니다. 거래소의 초당 허용 횟수와 가중치는 공지 없이 바뀝니다. 구체적인 숫자는 HTX 공식 문서의 Rate Limit 페이지에서 현재 값을 확인하고, 코드에는 상수 하나로 빼서 나중에 고치기 쉽게 만드세요.
5. account-id 없이는 주문이 안 나간다
서명을 통과해도 주문 단계에서 다시 막히는 지점이 있습니다. HTX 현물 주문은 계정 ID를 명시해야 합니다. 3장에서 받은 응답에서 type이 spot이고 state가 working인 항목의 id를 씁니다.
accounts = r.json()["data"]
spot_id = next(a["id"] for a in accounts
if a["type"] == "spot" and a["state"] == "working")
print(spot_id) # → 1234567
마진·선물 계정 ID를 잘못 넣으면 잔고가 있어도 account-frozen-balance-insufficient-error 계열 오류가 납니다. 이 값은 계정마다 고정이므로 기동 시 한 번 조회해 캐시하면 됩니다.
거래소 API는 규칙이 조금씩 달라서, 처음 붙일 때 대부분 여기서 시간을 씁니다. 알고랩이 HTX·바이낸스·Bybit·업비트 봇을 만들며 쌓은 구성 그대로 붙여 드립니다.
어떤 전략인지만 알려주세요 — 무료 상담 →6. 첫 주문 — POST /v1/order/orders/place
이제 실제 주문입니다. 다시 강조하면 서명은 쿼리스트링, 주문 내용은 body입니다.
path = "/v1/order/orders/place"
qs = signed_params("POST", path) # 인증 파라미터 4개 + Signature
body = {
"account-id": str(spot_id),
"symbol": "btcusdt",
"type": "buy-limit", # buy-market / sell-limit / sell-market
"amount": "0.0005", # 수량은 문자열로
"price": "60000", # 시장가면 이 필드를 뺀다
"source": "spot-api",
}
resp = requests.post(f"https://{HOST}{path}", params=qs,
data=json.dumps(body),
headers={"Content-Type": "application/json"},
timeout=10)
print(resp.json())
{ "status": "ok", "data": "987654321012345" } # data = 주문 ID
주의할 점 둘. 수량과 가격은 문자열로 보내야 하고(부동소수점 오차 방지), 심볼은 소문자입니다(BTCUSDT가 아니라 btcusdt). 시장가 매수의 amount는 수량이 아니라 지불할 USDT 금액으로 해석되는 점도 문서에서 확인하세요 — 여기서 자릿수를 착각해 예상보다 큰 주문이 나가는 사고가 종종 납니다.
첫 주문은 반드시 즉시 취소까지 세트로. 체결되지 않을 가격의 지정가로 넣고 POST /v1/order/orders/{order-id}/submitcancel로 바로 취소해 보세요. 주문·취소가 한 번에 돌아야 봇의 최소 왕복이 검증됩니다. 실전 투입 전 검증 절차는 실전 투입 전 3단계 검증에 정리했습니다.
7. ccxt로 우회하는 선택지
서명을 직접 구현하지 않는 방법도 있습니다. ccxt는 htx를 지원하므로 정렬·인코딩·타임스탬프를 라이브러리가 처리합니다.
import ccxt
ex = ccxt.htx({"apiKey": ACCESS_KEY, "secret": SECRET_KEY,
"enableRateLimit": True})
print(ex.fetch_balance()["total"])
order = ex.create_limit_buy_order("BTC/USDT", 0.0005, 60000)
빠르게 시작할 때는 유리합니다. 다만 두 가지를 감안하세요. 거래소 고유 파라미터나 신규 엔드포인트는 반영이 늦을 수 있고, 오류가 한 겹 감싸져서 원인 파악이 오히려 어려워집니다. 실전 봇이라면 ccxt를 쓰더라도 원본 응답을 남기는 로깅을 함께 두는 편이 낫습니다.
8. 봇으로 굴리기 전 확인할 것
- 키 권한은 최소로 — 자동매매에 출금 권한은 필요 없습니다. IP 화이트리스트도 걸어 두세요. 이유는 API 키 보안과 계정 보호에 정리했습니다.
- 키를 코드에 넣지 않기 — 환경변수나 별도 설정 파일로 분리하고, 저장소에 올리지 마세요.
- 24시간 운영은 VPS에서 — 개인 PC는 절전·업데이트로 끊깁니다(VPS 24시간 봇 운영).
- 세무·규제는 별도 확인 — 해외 거래소 이용 가능 여부, 신고 의무, 과세 처리는 시기와 개인 상황에 따라 다릅니다. 자동매매 세금 가이드는 개괄이며, 실제 신고는 전문가와 상담하세요.
거래소를 아직 고르는 중이라면 아래 관련 글의 Bybit 편과 함께 비교해 보시고, HTX 봇 제작 범위는 HTX 자동매매 봇 시작 페이지에 정리돼 있습니다.
자주 묻는 질문
Q. api-signature-not-valid는 왜 뜨나요?
서명 대상 4줄 문자열을 잘못 만든 경우가 대부분입니다. 메서드 대문자, 호스트만(프로토콜 제외), ASCII 정렬, URL 인코딩된 값, 그리고 Base64 인코딩 — 이 다섯을 순서대로 점검하세요.
Q. 타임스탬프 형식이 어떻게 되나요?
밀리초 정수가 아니라 UTC 날짜·시간 문자열(%Y-%m-%dT%H:%M:%S)입니다. KST로 만들면 9시간 차이로 거부됩니다.
Q. account-id는 꼭 필요한가요?
네. GET /v1/account/accounts로 type이 spot인 계정 ID를 받아 주문에 넣어야 합니다. 기동 시 한 번 조회해 캐시하면 됩니다.
Q. ccxt를 써도 되나요?
됩니다. 서명 문제를 건너뛸 수 있습니다. 다만 신규 엔드포인트 반영이 늦을 수 있고 오류 원인이 가려지므로 원본 응답 로깅을 함께 두세요.
Q. 한국에서 HTX 봇을 돌려도 되나요?
이용 가능 여부·원화 입출금·세무 처리는 시기와 개인 상황에 따라 다릅니다. 이 글은 기술 문서이며 이용을 권유하지 않습니다. 거래소 공식 공지와 관련 기관 안내를 직접 확인하세요.
마무리
HTX API가 유난히 어렵다는 인상은 사실 규칙이 바이낸스와 셋이나 다르기 때문입니다. 4줄 서명 문자열, Base64, UTC 문자열 타임스탬프 — 이 셋만 맞추면 나머지는 다른 거래소와 크게 다르지 않습니다. 그리고 서명을 통과한 다음의 진짜 일은 account-id 캐싱, 레이트리밋, 재시도, 로깅처럼 계속 돌아가게 만드는 부분입니다.
그 부분까지 한 번에 만들어 두고 싶다면 알고랩이 도와드립니다. 전략과 거래소만 알려주시면 됩니다.