---
updatedAt: 2026-10-27T11:20:50.000Z
agentTools:
  projectIndex: https://apidocs.bithumb.com/llms.txt
---

# 인증 토큰 생성하기

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

<Callout icon="fa-book" theme="default">
  API Key가 아직 없다면 [빠른 시작 가이드 > API Key 발급](doc:빠른-시작-가이드#api-key-발급)을 먼저 확인하세요.
</Callout>

## 인증 흐름

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

1. API Key와 Secret Key 준비
2. JWT Payload 구성
3. Secret Key로 서명 (HS256)
4. Authorization 헤더에 Bearer 토큰 포함
5. API 요청 전송

```http
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**   | 인증 정보를 담는 클레임(아래 상세)                       |
| **Signature** | Secret Key로 생성한 HMAC-SHA256 서명             |

## Payload 필드

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

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

### 파라미터가 없는 경우

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

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

### 파라미터가 있는 경우

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

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

## 코드 예시

### 파라미터 없는 요청

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

<Tabs>
  <Tab title="JavaScript">
    ```javascript
    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)
        });
    ```
  </Tab>

  <Tab title="Python">
    ```python
    # Python 3
    # pip3 installl pyJwt
    import jwt 
    import uuid
    import time
    import requests

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

    # Generate access token
    payload = {
        'access_key': accessKey,
        'nonce': str(uuid.uuid4()),
        'timestamp': round(time.time() * 1000)
    }
    jwt_token = jwt.encode(payload, secretKey)
    authorization_token = 'Bearer {}'.format(jwt_token)
    headers = {
      'Authorization': authorization_token
    }

    try:
        # Call API
        response = requests.get(apiUrl + '/v1/accounts', headers=headers)
        # handle to success or fail
        print(response.status_code)
        print(response.json())
    except Exception as err:
        # handle exception
        print(err)
    ```
  </Tab>

  <Tab title="Java">
    ```java
    package com.example.sample;

    // https://mvnrepository.com/artifact/com.auth0/java-jwt
    import com.auth0.jwt.JWT;
    import com.auth0.jwt.algorithms.Algorithm;
    // https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient
    import org.apache.http.client.methods.CloseableHttpResponse;
    import org.apache.http.client.methods.HttpGet;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClients;
    import org.apache.http.util.EntityUtils;

    import java.nio.charset.StandardCharsets;
    import java.util.UUID;

    public class GETNoArgs {

        public static void main(String[] args) {
            String accessKey = "발급받은 API KEY";
            String secretKey = "발급받은 SECRET KEY";
            String apiUrl = "https://api.bithumb.com";
            
            // Generate access token
            Algorithm algorithm = Algorithm.HMAC256(secretKey);
            String jwtToken = JWT.create()
                    .withClaim("access_key", accessKey)
                    .withClaim("nonce", UUID.randomUUID().toString())
                    .withClaim("timestamp", System.currentTimeMillis())
                    .sign(algorithm);
            String authenticationToken = "Bearer " + jwtToken;

            // Call API
            final HttpGet httpRequest = new HttpGet(apiUrl + "/v1/accounts");
            httpRequest.addHeader("Authorization", authenticationToken);

            try (CloseableHttpClient client = HttpClients.createDefault();
                 CloseableHttpResponse response = client.execute(httpRequest)) {
                // handle to response
                int httpStatus = response.getStatusLine().getStatusCode();
                String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
                System.out.println(httpStatus);
                System.out.println(responseBody);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    ```
  </Tab>
</Tabs>

### 파라미터 있는 요청

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

<Tabs>
  <Tab title="JavaScript">
    ```javascript
    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)
        })
    ```
  </Tab>

  <Tab title="Python">
    ```python
    # Python 3
    # pip3 installl pyJwt
    import jwt 
    import uuid
    import hashlib
    import time
    from urllib.parse import urlencode
    import requests

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

    # Set API parameters
    param = dict( market='KRW-BTC' )

    # Generate access token
    query = urlencode(param).encode()
    hash = hashlib.sha512()
    hash.update(query)
    query_hash = hash.hexdigest()
    payload = {
        'access_key': accessKey,
        'nonce': str(uuid.uuid4()),
        'timestamp': round(time.time() * 1000), 
        'query_hash': query_hash,
        'query_hash_alg': 'SHA512',
    }   
    jwt_token = jwt.encode(payload, secretKey)
    authorization_token = 'Bearer {}'.format(jwt_token)
    headers = {
      'Authorization': authorization_token
    }

    try:
        # Call API
        response = requests.get(apiUrl + '/v1/orders/chance', params=param, headers=headers)
        # handle to success or fail
        print(response.status_code)
        print(response.json())
    except Exception as err:
        # handle exception
        print(err)
    ```
  </Tab>

  <Tab title="Java">
    ```java
    package com.example.sample;

    // https://mvnrepository.com/artifact/com.auth0/java-jwt
    import com.auth0.jwt.JWT;
    import com.auth0.jwt.algorithms.Algorithm;
    // https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient
    import org.apache.http.NameValuePair;
    import org.apache.http.client.methods.CloseableHttpResponse;
    import org.apache.http.client.methods.HttpGet;
    import org.apache.http.client.utils.URLEncodedUtils;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClients;
    import org.apache.http.message.BasicNameValuePair;
    import org.apache.http.util.EntityUtils;

    import java.math.BigInteger;
    import java.nio.charset.StandardCharsets;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.UUID;

    public class GETArgs {

        public static void main(String[] args) throws NoSuchAlgorithmException {
            String accessKey = "발급받은 API KEY";
            String secretKey = "발급받은 SECRET KEY";
            String apiUrl = "https://api.bithumb.com";

            // Set API parameters
            List<NameValuePair> queryParams = new ArrayList<>();
            queryParams.add(new BasicNameValuePair("market", "KRW-BTC"));

            // Generate access token
            String query = URLEncodedUtils.format(queryParams, StandardCharsets.UTF_8);
            MessageDigest md = MessageDigest.getInstance("SHA-512");
            md.update(query.getBytes(StandardCharsets.UTF_8));
            String queryHash = String.format("%0128x", new BigInteger(1, md.digest()));
            Algorithm algorithm = Algorithm.HMAC256(secretKey);
            String jwtToken = JWT.create()
                    .withClaim("access_key", accessKey)
                    .withClaim("nonce", UUID.randomUUID().toString())
                    .withClaim("timestamp", System.currentTimeMillis())
                    .withClaim("query_hash", queryHash)
                    .withClaim("query_hash_alg", "SHA512")
                    .sign(algorithm);
            String authenticationToken = "Bearer " + jwtToken;

            // Call API
            final HttpGet httpRequest = new HttpGet(apiUrl + "/v1/orders/chance?" + query);
            httpRequest.addHeader("Authorization", authenticationToken);

            try (CloseableHttpClient client = HttpClients.createDefault();
                 CloseableHttpResponse response = client.execute(httpRequest)) {
                // handle to response
                int httpStatus = response.getStatusLine().getStatusCode();
                String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
                System.out.println(httpStatus);
                System.out.println(responseBody);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    ```
  </Tab>
</Tabs>

## query\_hash 상세 규칙

`query_hash`는 **GET 쿼리 파라미터**와 **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 해싱
```

<Tabs>
  <Tab title="JavaScript">
    ```javascript
    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');
    ```
  </Tab>

  <Tab title="Python">
    ```python
    import hashlib
    from urllib.parse import urlencode

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

    # body 파라미터를 쿼리 문자열로 변환
    query = urlencode(request_body)

    hash_obj = hashlib.sha512()
    hash_obj.update(query.encode('utf-8'))
    query_hash = hash_obj.hexdigest()
    ```
  </Tab>

  <Tab title="Java">
    ```java
    import java.math.BigInteger;
    import java.nio.charset.StandardCharsets;
    import java.security.MessageDigest;
    import java.util.LinkedHashMap;
    import java.util.Map;

    Map<String, String> requestBody = new LinkedHashMap<>();
    requestBody.put("market", "KRW-BTC");
    requestBody.put("side", "bid");
    requestBody.put("order_type", "limit");
    requestBody.put("price", "80000000");
    requestBody.put("volume", "0.001");

    // body 파라미터를 쿼리 문자열로 변환
    String query = requestBody.entrySet().stream()
            .map(entry -> entry.getKey() + "=" + entry.getValue())
            .collect(java.util.stream.Collectors.joining("&"));

    MessageDigest md = MessageDigest.getInstance("SHA-512");
    md.update(query.getBytes(StandardCharsets.UTF_8));
    String queryHash = String.format("%0128x", new BigInteger(1, md.digest()));
    ```
  </Tab>
</Tabs>

### 배열 파라미터 처리

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

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

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

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

❌ 잘못된 쿼리 문자열:
order_ids=id1,id2,id3
```

<Tabs>
  <Tab title="JavaScript">
    ```javascript
    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');
    ```
  </Tab>

  <Tab title="Python">
    ```python
    import hashlib

    order_ids = ['id1', 'id2', 'id3']

    # 배열 파라미터를 key[]=value 형태로 변환
    query = '&'.join([f'order_ids[]={oid}' for oid in order_ids])

    hash_obj = hashlib.sha512()
    hash_obj.update(query.encode('utf-8'))
    query_hash = hash_obj.hexdigest()
    ```
  </Tab>

  <Tab title="Java">
    ```java
    import java.math.BigInteger;
    import java.nio.charset.StandardCharsets;
    import java.security.MessageDigest;
    import java.util.List;

    List<String> orderIds = List.of("id1", "id2", "id3");

    // 배열 파라미터를 key[]=value 형태로 변환
    String query = orderIds.stream()
            .map(id -> "order_ids[]=" + id)
            .collect(java.util.stream.Collectors.joining("&"));

    MessageDigest md = MessageDigest.getInstance("SHA-512");
    md.update(query.getBytes(StandardCharsets.UTF_8));
    String queryHash = String.format("%0128x", new BigInteger(1, md.digest()));
    ```
  </Tab>
</Tabs>

## 주의사항

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

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

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

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

전체 에러 코드는 [API 주요 에러 코드](doc:api-주요-에러-코드) 문서를 참고하세요.

## 다음 단계

<Cards>
  <Card title="API 호출해 보기" href="/docs/api-호출해-보기" icon="fa-solid fa-code">
    생성한 토큰으로 주문 가능 정보 조회, 주문, 주문 취소까지 실전 시나리오를 다룹니다.
  </Card>

  <Card title="API 레퍼런스" href="/reference/api-레퍼런스" icon="fa-solid fa-book">
    전체 엔드포인트의 파라미터, 응답 스키마, 코드 샘플을 확인합니다.
  </Card>
</Cards>