인증 토큰 생성하기

빗썸 Private API 호출에 필요한 JWT 인증 토큰의 구조와 생성 규칙을 설명합니다

API Key가 아직 없다면 빠른 시작 가이드 > API Key 발급을 먼저 확인하세요.

인증 흐름

Private API는 요청마다 JWT(JSON Web Token) 인증 토큰을 생성하여 Authorization 헤더에 포함해야 합니다.

  1. API Key와 Secret Key 준비
  2. JWT Payload 구성
  3. Secret Key로 서명 (HS256)
  4. Authorization 헤더에 Bearer 토큰 포함
  5. API 요청 전송
GET /v1/accounts HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json; charset=utf-8

JWT 구조

빗썸 API의 JWT는 세 부분으로 구성됩니다.

구성내용
Header{"alg": "HS256", "typ": "JWT"} — 서명 알고리즘
Payload인증 정보를 담는 클레임(아래 상세)
SignatureSecret Key로 생성한 HMAC-SHA256 서명

Payload 필드

요청에 파라미터가 있는지 여부에 따라 포함하는 필드가 달라집니다.

필드타입필수설명
access_keystring✅ 항상발급받은 API Key
noncestring✅ 항상요청마다 고유한 값(UUID 권장)
timestampinteger✅ 항상현재 시각(밀리초 단위 Unix timestamp)
query_hashstring파라미터 있을 때쿼리 문자열의 SHA-512 해시값
query_hash_algstring파라미터 있을 때해시 알고리즘("SHA512" 고정)

파라미터가 없는 경우

access_key, nonce, timestamp 3개 필드만 포함합니다.

{
  "access_key": "L7rVaYfBIc2BDsnlQGfkR93d6DoOAJCw7mJr5Eso",
  "nonce": "6f5570df-d8bc-4daf-85b4-976733feb624",
  "timestamp": 1712230310689
}

파라미터가 있는 경우

쿼리 문자열을 SHA-512로 해싱하여 query_hashquery_hash_alg를 추가합니다.

{
  "access_key": "L7rVaYfBIc2BDsnlQGfkR93d6DoOAJCw7mJr5Eso",
  "nonce": "6f5570df-d8bc-4daf-85b4-976733feb624",
  "timestamp": 1712230310689,
  "query_hash": "1c2362ca9d79947582cae192acf63efb8756caa49af1eb64b12ba45617165431...",
  "query_hash_alg": "SHA512"
}

코드 예시

파라미터 없는 요청

GET /v1/accounts(전체 계좌 조회)와 같이 쿼리 파라미터가 없는 요청에 사용합니다.

const jwt = require('jsonwebtoken');
const { v4: uuidv4 } = require('uuid');
const axios = require('axios')

const accessKey = '발급받은 API KEY'
const secretKey = '발급받은 SECRET KEY'
const apiUrl = 'https://api.bithumb.com'

// Generate access token
const payload = {
    access_key: accessKey,
    nonce: uuidv4(),
    timestamp: Date.now()
};
const jwtToken = jwt.sign(payload, secretKey)
const config = {
    headers: {
        Authorization: `Bearer ${jwtToken}`
    }
}

// Call API
axios.get(apiUrl + '/v1/accounts', config)
    .then((response) => {
        // handle to success
        console.log('status: ', response.status)
        console.log('data: ', response.data)
    })
    .catch((error) => {
        // handle to fail
        console.log(error.response.status)
        console.log(error.response.data)
    });

파라미터 있는 요청

쿼리 파라미터가 있는 요청(예: GET /v1/orders/chance?market=KRW-BTC)의 경우, 파라미터를 해싱하여 JWT payload에 포함해야 합니다. 아래는 주문 가능 정보 조회 API 호출 예시입니다.

const jwt = require('jsonwebtoken');
const { v4: uuidv4 } = require('uuid');
const crypto = require('crypto');
const querystring = require('querystring');
const axios = require('axios')

const accessKey = '발급받은 API KEY'
const secretKey = '발급받은 SECRET KEY'
const apiUrl = 'https://api.bithumb.com'

// Set API parameters
const query = 'market=KRW-BTC'

// Generate access token
const alg = 'SHA512'
const hash = crypto.createHash(alg)
const queryHash = hash.update(query, 'utf-8').digest('hex')
const payload = {
    access_key: accessKey,
    nonce: uuidv4(),
    timestamp: Date.now(),
    query_hash: queryHash,
    query_hash_alg: alg
}
const jwtToken = jwt.sign(payload, secretKey)
const config = {
    headers: {
        Authorization: `Bearer ${jwtToken}`
    }
}

// Call API
axios.get(apiUrl + '/v1/orders/chance?' + query, config)
    .then((response) => {
        // handle to success
        console.log('status: ', response.status)
        console.log('data: ', response.data)
    })
    .catch((error) => {
        // handle to fail
        console.log(error.response.status)
        console.log(error.response.data)
    })

query_hash 상세 규칙

query_hashGET 쿼리 파라미터POST/DELETE body 파라미터 모두에 적용됩니다. 파라미터를 쿼리 문자열 형태로 변환한 뒤 SHA-512로 해싱합니다.

POST 요청 — body 파라미터

body의 JSON 파라미터를 쿼리 문자열 형태로 변환한 뒤 동일하게 해싱합니다.

POST /v2/orders
Body: {"market": "KRW-BTC", "side": "bid", "order_type": "limit", "price": "80000000", "volume": "0.001"}

→ 쿼리 문자열로 변환: "market=KRW-BTC&side=bid&order_type=limit&price=80000000&volume=0.001"
→ 이 문자열을 SHA-512 해싱
const crypto = require('crypto');

const requestBody = {
  market: 'KRW-BTC',
  side: 'bid',
  order_type: 'limit',
  price: '80000000',
  volume: '0.001',
};

// body 파라미터를 쿼리 문자열로 변환
const query = Object.entries(requestBody)
  .map(([key, value]) => `${key}=${value}`)
  .join('&');

const hash = crypto.createHash('SHA512');
const queryHash = hash.update(query, 'utf-8').digest('hex');

배열 파라미터 처리

파라미터에 배열이 포함된 경우, 쿼리 문자열 형태에 주의해야 합니다. 배열의 각 요소를 key[]=value 형태로 개별 전개합니다.

예를 들어 여러 주문을 조회할 때:

파라미터: { "order_ids": ["id1", "id2", "id3"] }

✅ 올바른 쿼리 문자열:
order_ids[]=id1&order_ids[]=id2&order_ids[]=id3

❌ 잘못된 쿼리 문자열:
order_ids=id1,id2,id3
const crypto = require('crypto');
const querystring = require('querystring');

const params = {
  order_ids: ['id1', 'id2', 'id3']
};

// querystring.stringify는 배열을 자동으로 key=v1&key=v2 형태로 변환
// 하지만 빗썸 API는 key[]=v1&key[]=v2 형태가 필요
const query = params.order_ids
  .map(id => `order_ids[]=${id}`)
  .join('&');

const hash = crypto.createHash('SHA512');
const queryHash = hash.update(query, 'utf-8').digest('hex');

주의사항

항목설명
토큰 재사용 금지요청마다 새로운 noncetimestamp로 토큰을 생성하세요. 동일 토큰을 재사용하면 인증이 거부됩니다.
Secret Key 보안Secret Key는 발급 시 한 번만 표시됩니다. 소스 코드에 직접 작성하지 말고 환경 변수나 .env 파일로 관리하세요. (개발 환경 설정하기 참고)
Content-TypePOST, DELETE 요청 시 body가 있으면 Content-Type: application/json; charset=utf-8 헤더를 포함하세요.
query_hash 누락파라미터가 있는데 query_hash를 빠뜨리면 401 Unauthorized 에러가 발생합니다.
배열 파라미터key[]=value 형태로 전개해야 합니다. 쉼표 구분(key=v1,v2)은 올바른 해시를 생성하지 않습니다.

자주 발생하는 에러와 해결 방법

인증 관련 오류는 대부분 JWT 서명이 올바르게 되지 않았을 때 발생합니다.

HTTP 상태 코드에러 코드원인해결 방법
401invalid_query_payloadJWT query 검증 실패query_hash가 요청 파라미터와 일치하는지 확인하세요.
401jwt_verificationJWT 토큰 검증 실패Secret Key와 서명 알고리즘(HS256)을 확인하세요.
401expired_jwtJWT 만료요청마다 새로운 nonce와 timestamp를 생성하세요.
401NotAllowIP허용되지 않은 IP 접근빗썸 사이트에서 등록한 IP 주소를 확인하세요.
401out_of_scopeAPI Key 권한 부족빗썸 사이트에서 API 활성화 항목을 확인하세요.

전체 에러 코드는 API 주요 에러 코드 문서를 참고하세요.

다음 단계