> For the complete documentation index, see [llms.txt](https://developers-apps-in-toss.toss.im/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers-apps-in-toss.toss.im/documentation/api-and-sdk-zh/common/authentication/toss-auth.md).

# Toss 认证

> [签约 Toss 认证](https://app.gitbook.com/s/8pQgXiR5QAzduV54W8Om/authentication/contract) >
>
> 为您说明使用 Toss 认证服务的签约方式。

{% hint style="info" %}
**请确认最低版本**

* **SDK** : 1.2.1 以上
* **Toss App（身份验证）** : 5.233.0 以上
* **Toss App（一次认证）** : 5.236.0 以上

请使用 getTossAppVersion 函数检查 Toss App 版本。
{% endhint %}

### 防火墙设置

请求服务器的 **出站(Outbound)** 请在设置中允许以下 Toss 认证 IP。所有通信均通过 **443 端口(HTTPS)** 。

Toss 认证服务器的 **入站(Inbound)** 开放且无限制，因此无需额外设置即可直接通信。

{% hint style="info" %}
**身份验证 IP**

* 117.52.3.222
* 117.52.3.235
* 211.115.96.222
* 211.115.96.235
  {% endhint %}

### 1. 获取 AccessToken

用于 Toss 身份验证的 **Access Token**会发放。发放后的令牌将用于之后所有 API 调用的 **Authorization** Header。

令牌包含 **过期时间(`expires_in`)** 。到期时需要重新发放新令牌， **如果已有有效令牌，请避免重复发放，** 减少不必要的调用。

* Base URL: `https://oauth2.cert.toss.im`
* Endpoint: `/token`
* Method: `POST`
* Content-Type: `application/x-www-form-urlencoded`

**请求头**

| 名称           | 类型     | 是否必填 | 说明                                  |
| ------------ | ------ | ---- | ----------------------------------- |
| Content-Type | string | Y    | `application/x-www-form-urlencoded` |

**请求参数**

| 名称             | 类型     | 是否必填 | 说明                        |
| -------------- | ------ | ---- | ------------------------- |
| grant\_type    | string | Y    | 固定值： `client_credentials` |
| scope          | string | Y    | 认证请求范围（例如： `ca`)          |
| client\_id     | string | Y    | 发放给客户公司的客户端 ID            |
| client\_secret | string | Y    | 发放给客户公司的客户端 Secret        |

{% tabs %}
{% tab title="Shell(curl)" %}

```bash
curl --request POST 'https://oauth2.cert.toss.im/token' \\
--header 'Content-Type: application/x-www-form-urlencoded' \\
--data-urlencode 'grant_type=client_credentials' \\
--data-urlencode 'client_id=test_a8e23336d673ca70922b485fe806eb2d' \\
--data-urlencode 'client_secret=test_418087247d66da09fda1964dc4734e453c7cf66a7a9e3' \\
--data-urlencode 'scope=ca'
```

{% endtab %}

{% tab title="java" %}

```java
URL url = new URL("https://oauth2.cert.toss.im/token");
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
httpConn.setRequestMethod("POST");

httpConn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
httpConn.setDoOutput(true);
OutputStreamWriter writer = new OutputStreamWriter(httpConn.getOutputStream());
writer.write("grant_type=client_credentials&" +
        "client_id=test_a8e23336d673ca70922b485fe806eb2d&" +
        "client_secret=test_418087247d66da09fda1964dc4734e453c7cf66a7a9e3&" +
        "scope=ca");
writer.flush();
writer.close();

httpConn.getOutputStream().close();
InputStream responseStream = httpConn.getResponseCode() == 200
        ? httpConn.getInputStream()
        : httpConn.getErrorStream();
Scanner s = new Scanner(responseStream).useDelimiter("\\A");
String response = s.hasNext() ? s.next() : "";
System.out.println(response);
```

{% endtab %}

{% tab title="php" %}

```php
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://oauth2.cert.toss.im/token');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type' : 'application/x-www-form-urlencoded',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, 'grant_type=client_credentials&client_id=test_a8e23336d673ca70922b485fe806eb2d&client_secret=test_418087247d66da09fda1964dc4734e453c7cf66a7a9e3&scope=ca');

$response = curl_exec($ch);

curl_close($ch);
```

{% endtab %}
{% endtabs %}

**响应**

| 名称            | 类型     | 说明                |
| ------------- | ------ | ----------------- |
| access\_token | string | Access Token 值    |
| scope         | string | 已发放的认证范围          |
| token\_type   | string | 令牌类型（始终 `Bearer`) |
| expires\_in   | number | 令牌过期时间（秒）         |

```json
{
  "access_token": "eyJraWQiOiJjZXJ0IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJ0ZXN0X2E4ZTIzMzM2ZDY3M2NhNzA5MjJiNDg1ZmU4MDZlYjJkIiwiYXVkIjoidGVzdF9hOGUyMzMzNmQ2NzNjYTcwOTIyYjQ4NWZlODA2ZWIyZCIsIm5iZiI6MTY0OTIyMjk3OCwic2NvcGUiOlsiY2EiXSwiaXNzIjoiaHR0cHM6XC9cL2NlcnQudG9zcy5pbSIsImV4cCI6MTY4MDc1ODk3OCwiaWF0IjoxNjQ5MjIyOTc4LCJqdGkiOiI4MDNjNDBjOC1iMzUxLTRmOGItYTIxNC1iNjc5MmNjMzBhYTcifQ.cjDZ0lAXbuf-KAgi3FlG1YGxvgvT3xrOYKDTstfbUz6CoNQgvd9TqI6RmsGZuona9jIP6H12Z1Xb07RIfAVoTK-J9iC5_Yp8ZDdcalsMNj51pPP8wso86rn-mKsrx1J5Rdi3GU58iKt0zGr4KzqSxUJkul9G4rY03KInwvl692HU19kYA9y8uTI4bBX--UPfQ02G0QH9HGTPHs7lZsISDtyD8sB2ikz5p7roua7U467xWy4BnRleCEWO2uUaNNGnwd7SvbjhmsRZqohs9KzDUsFjVhSiRNdHL53XJQ5zFHwDF92inRZFLu6Dw8xttPtNHwAD1kT84uXJcVMfEHtwkQ",
  "scope": "ca",
  "token_type": "Bearer",
  "expires_in": 31536000
}
```

### 2. 发起认证请求

在 Toss 认证服务器上 `txId`后发放并开始身份验证流程。

* BaseURL : `https://cert.toss.im`
* Endpoint : `/api/v2/sign/user/auth/request`
* Method : `POST`
* Content-type : `application/json`

#### 2-1. 基于个人信息的认证

客户的 **姓名·出生日期·电话号码** 将 **加密后传输**的方式。为确保安全， [会话密钥(sessionKey)](#세션키-생성)请在每次请求时重新生成。

**请求头**

| 名称            | 类型     | 是否必填 | 说明                      |
| ------------- | ------ | ---- | ----------------------- |
| Authorization | string | Y    | `Bearer {Access Token}` |
| Content-Type  | string | Y    | `application/json`      |

**请求参数**

| 名称           | 类型     | 是否必填 | 说明                                      |
| ------------ | ------ | ---- | --------------------------------------- |
| requestUrl   | string | Y    | 使用 Toss 身份验证时返回的客户公司 App Scheme         |
| requestType  | string | Y    | `USER_PERSONAL`                         |
| triggerType  | string | Y    | `APP_SCHEME`                            |
| userName     | string | Y    | [加密](#개인정보-암복호화) 必填                     |
| userPhone    | string | Y    | 仅限数字， [加密](#개인정보-암복호화) 必填               |
| userBirthday | string | Y    | `YYYYMMDD`, [加密](#개인정보-암복호화) 必填         |
| sessionKey   | string | Y    | 用于 AES 加解密，每次请求都需要新生成 [（生成方法）](#세션키-생성) |

**请求示例**

{% tabs %}
{% tab title="Shell(curl)" %}

```bash
curl --location --request POST 'https://cert.toss.im/api/v2/sign/user/auth/request' --header 'Authorization: Bearer eyJraWQiOiJjZXJ0IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJ0ZXN0X2E4ZTIzMzM2ZDY3M2NhNzA5MjJiNDg1ZmU4MDZlYjJkIiwiYXVkIjoidGVzdF9hOGUyMzMzNmQ2NzNjYTcwOTIyYjQ4NWZlODA2ZWIyZCIsIm5iZiI6MTY0OTIyMjk3OCwic2NvcGUiOlsiY2EiXSwiaXNzIjoiaHR0cHM6XC9cL2NlcnQudG9zcy5pbSIsImV4cCI6MTY4MDc1ODk3OCwiaWF0IjoxNjQ5MjIyOTc4LCJqdGkiOiI4MDNjNDBjOC1iMzUxLTRmOGItYTIxNC1iNjc5MmNjMzBhYTcifQ.cjDZ0lAXbuf-KAgi3FlG1YGxvgvT3xrOYKDTstfbUz6CoNQgvd9TqI6RmsGZuona9jIP6H12Z1Xb07RIfAVoTK-J9iC5_Yp8ZDdcalsMNj51pPP8wso86rn-mKsrx1J5Rdi3GU58iKt0zGr4KzqSxUJkul9G4rY03KInwvl692HU19kYA9y8uTI4bBX--UPfQ02G0QH9HGTPHs7lZsISDtyD8sB2ikz5p7roua7U467xWy4BnRleCEWO2uUaNNGnwd7SvbjhmsRZqohs9KzDUsFjVhSiRNdHL53XJQ5zFHwDF92inRZFLu6Dw8xttPtNHwAD1kT84uXJcVMfEHtwkQ' --header 'Content-Type: application/json' # 会话密钥需在每次请求时重新生成。
--data-raw '{
       "requestType" : "USER_PERSONAL",
       "requestUrl" : "intoss://my-granite-app",
       "triggerType" : "APP_SCHEME",
       "userName" : "v1$cc575847-f549-4c1e-89c7-eff11743e05e$5AfwdVLSmDoxBERDIV8gDny2QLcOzYOqvgt1l4gqEA==",
       "userPhone" : "v1$cc575847-f549-4c1e-89c7-eff11743e05e$OKtwqMR/RI+N3vx0FNtcx8GAoejDq5lb3wIr",
       "userBirthday" : "v1$cc575847-f549-4c1e-89c7-eff11743e05e$OaNxoMR2RYaPiH7km5yJyZQ472+uWNEy",
       "sessionKey" : "v1$cc575847-f549-4c1e-89c7-eff11743e05e$XTTyBJntTja9NfUaTaO09bQCtEApnn3dd7lN8s+jPA6qn5q5kBbSJEptazpSMqGFyB7P0XhnJSkRwukAuunesbm+e0p5tdQ7wiOkauM44FvZj/IwETTA74iLZTNrwmE3aYXv8b1wbIfQx/oT8k9+XNEPkHA0foCFtjF8MRnyjwpzR4hoi2sFk33xhoJa46kLGxz7d3z6r/KYKMFbwkQFOm81Nk8W+oJkT0AjdlOD075QrJ4zm9VReVvE4fT4Q1jY/5VzROt4GkqVvrziYbWRp9/v1/ETVyi5Lf+MceWHLS1MGicqUXfrfnFdqvOcZZytUkvb0AAyg75Sr5tgja55ma3t5AEu65IrO1Cop4wS/XhIwKpWUrMav5JI5X1iZ1tRznE7VRT/dsRLjgIX/wtZajY2ATG+feld2mmxD/mP/ET3JXsYKfmN3DkO10fQZY9915eUyDYm7NMS/U3l+VP8wMzd5WpWVjfxUvYP5eRwPM83hG9wFhHXV4ykodiX0BLRoERXou416uKDJR61b8xFFX+iDPnOfaeROlFFWj6zbK4tPfjRzyaWVQMmSM8igq7iBglehFo+EyyQnAAcUeda+P/7fQmwFDE1a8bQuXFBCwxNOOyPiJLV2+29pzKELcHa+WCrvcbHkOgG4EwjHHWmd17vUVXZGXOERsRuLQMM3mM=",
    }'
```

{% endtab %}

{% tab title="java" %}

```java
URL url = new URL("https://cert.toss.im/api/v2/sign/user/auth/request");
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
httpConn.setRequestMethod("POST");
httpConn.setRequestProperty("Authorization", "Bearer eyJraWQiOiJjZXJ0IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJ0ZXN0X2E4ZTIzMzM2ZDY3M2NhNzA5MjJiNDg1ZmU4MDZlYjJkIiwiYXVkIjoidGVzdF9hOGUyMzMzNmQ2NzNjYTcwOTIyYjQ4NWZlODA2ZWIyZCIsIm5iZiI6MTY0OTIyMjk3OCwic2NvcGUiOlsiY2EiXSwiaXNzIjoiaHR0cHM6XC9cL2NlcnQudG9zcy5pbSIsImV4cCI6MTY4MDc1ODk3OCwiaWF0IjoxNjQ5MjIyOTc4LCJqdGkiOiI4MDNjNDBjOC1iMzUxLTRmOGItYTIxNC1iNjc5MmNjMzBhYTcifQ.cjDZ0lAXbuf-KAgi3FlG1YGxvgvT3xrOYKDTstfbUz6CoNQgvd9TqI6RmsGZuona9jIP6H12Z1Xb07RIfAVoTK-J9iC5_Yp8ZDdcalsMNj51pPP8wso86rn-mKsrx1J5Rdi3GU58iKt0zGr4KzqSxUJkul9G4rY03KInwvl692HU19kYA9y8uTI4bBX--UPfQ02G0QH9HGTPHs7lZsISDtyD8sB2ikz5p7roua7U467xWy4BnRleCEWO2uUaNNGnwd7SvbjhmsRZqohs9KzDUsFjVhSiRNdHL53XJQ5zFHwDF92inRZFLu6Dw8xttPtNHwAD1kT84uXJcVMfEHtwkQ");
httpConn.setRequestProperty("Content-Type", "application/json");
// 会话密钥需在每次请求时重新生成。
httpConn.setDoOutput(true);
OutputStreamWriter writer = new OutputStreamWriter(httpConn.getOutputStream());
writer.write("{" +
        "\"requestType\" : \"USER_PERSONAL\"," +
        "\"triggerType\" : \"APP_SCHEME\"," +
        "\"userName\" : \"v1$255f8cc3-7d1d-4667-b28b-03f44e09483f$ZUyjGpmb/nL9W7//N1/VUN/F/947biU+1w==\"," +
        "\"userPhone\" : \"v1$255f8cc3-7d1d-4667-b28b-03f44e09483f$v8QTxScwJ/NBA+Gp/TXerMWQp9BrQ45M7xaC\"," +
        "\"userBirthday\" : \"v1$255f8cc3-7d1d-4667-b28b-03f44e09483f$vswaxSEyJf/J7Qndq2E7iOPJOmIWILby\"," +
        "\"sessionKey\" : \"v1$255f8cc3-7d1d-4667-b28b-03f44e09483f$MdeifC7fngWC9qn/qia1Itp6jYulvhTH1JCln0oYp3xg95VpnqN5O5isZ58ZYD1WM03YooETiwQIjtLEMl2HSjxwlXZQ2yoyd/zH3mgCzlVDd/nOO0AI04Rzo/M7mvdfoN/k+DUBAeQ4NBIC9uqMt0JrkpuGvv3O1faYPBCV6OCMIcAOAbQb9fJx1bWFRLMkhd3meTD9wt2px46Kz7kW/FZXefMJr4qdnJEX9pZKz2QMJCMEnu0aW+AkJQUS5VfPSohrFTS8VeWoiNArFSESIxZhX3A+duAIJrYxTvXvOCM9ntO9xIMwTd+hp9L1UAYFOrqh6J2gmN1nI5ScBvtkLwRMFQ1D/eoSL/HD7Xb9cBAxMxPX4dn0OjLkUuO0y+KW4s+Prj9IYesDEkXUQpuQKz+4mLfX88lyplJ7o2x3uoecFlIBT92oY6BI+yxTATnwSIK249OwX2vuG6HLSOXlI3RScHjEDtzz1zPK6kjCGkFBM+5+gMyTIF4C130Uk4rlXpxYOGwPoBKBD2buGbBh7AHIaFgHlYjShdPiHMCF0ZDQnOubk139cRzoCVkKqdpPgVqIy8q6982UXzeoSIxqpSauqaL0IK2PPbMy3s9WH12NrDFDkg4NLAhnEgGNfOPjMl9lRMW+i4cCw0jhaCx1bu5GTEQzDCpc0DFT+/KPkKc=\"" +
        "}");
writer.flush();
writer.close();
httpConn.getOutputStream().close();
InputStream responseStream = httpConn.getResponseCode() == 200
        ? httpConn.getInputStream()
        : httpConn.getErrorStream();
Scanner s = new Scanner(responseStream).useDelimiter("\\A");
String response = s.hasNext() ? s.next() : "";
System.out.println(response);
```

{% endtab %}

{% tab title="php" %}

```php
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://cert.toss.im/api/v2/sign/user/auth/request');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization' : 'Bearer eyJraWQiOiJjZXJ0IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJ0ZXN0X2E4ZTIzMzM2ZDY3M2NhNzA5MjJiNDg1ZmU4MDZlYjJkIiwiYXVkIjoidGVzdF9hOGUyMzMzNmQ2NzNjYTcwOTIyYjQ4NWZlODA2ZWIyZCIsIm5iZiI6MTY0OTIyMjk3OCwic2NvcGUiOlsiY2EiXSwiaXNzIjoiaHR0cHM6XC9cL2NlcnQudG9zcy5pbSIsImV4cCI6MTY4MDc1ODk3OCwiaWF0IjoxNjQ5MjIyOTc4LCJqdGkiOiI4MDNjNDBjOC1iMzUxLTRmOGItYTIxNC1iNjc5MmNjMzBhYTcifQ.cjDZ0lAXbuf-KAgi3FlG1YGxvgvT3xrOYKDTstfbUz6CoNQgvd9TqI6RmsGZuona9jIP6H12Z1Xb07RIfAVoTK-J9iC5_Yp8ZDdcalsMNj51pPP8wso86rn-mKsrx1J5Rdi3GU58iKt0zGr4KzqSxUJkul9G4rY03KInwvl692HU19kYA9y8uTI4bBX--UPfQ02G0QH9HGTPHs7lZsISDtyD8sB2ikz5p7roua7U467xWy4BnRleCEWO2uUaNNGnwd7SvbjhmsRZqohs9KzDUsFjVhSiRNdHL53XJQ5zFHwDF92inRZFLu6Dw8xttPtNHwAD1kT84uXJcVMfEHtwkQ',
    'Content-Type' : 'application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{
       "requestType" : "USER_PERSONAL",
       "triggerType" : "APP_SCHEME",
       "userName" : "v1$255f8cc3-7d1d-4667-b28b-03f44e09483f$ZUyjGpmb/nL9W7//N1/VUN/F/947biU+1w==",
       "userPhone" : "v1$255f8cc3-7d1d-4667-b28b-03f44e09483f$v8QTxScwJ/NBA+Gp/TXerMWQp9BrQ45M7xaC",
       "userBirthday" : "v1$255f8cc3-7d1d-4667-b28b-03f44e09483f$vswaxSEyJf/J7Qndq2E7iOPJOmIWILby",
       "sessionKey" : "v1$255f8cc3-7d1d-4667-b28b-03f44e09483f$MdeifC7fngWC9qn/qia1Itp6jYulvhTH1JCln0oYp3xg95VpnqN5O5isZ58ZYD1WM03YooETiwQIjtLEMl2HSjxwlXZQ2yoyd/zH3mgCzlVDd/nOO0AI04Rzo/M7mvdfoN/k+DUBAeQ4NBIC9uqMt0JrkpuGvv3O1faYPBCV6OCMIcAOAbQb9fJx1bWFRLMkhd3meTD9wt2px46Kz7kW/FZXefMJr4qdnJEX9pZKz2QMJCMEnu0aW+AkJQUS5VfPSohrFTS8VeWoiNArFSESIxZhX3A+duAIJrYxTvXvOCM9ntO9xIMwTd+hp9L1UAYFOrqh6J2gmN1nI5ScBvtkLwRMFQ1D/eoSL/HD7Xb9cBAxMxPX4dn0OjLkUuO0y+KW4s+Prj9IYesDEkXUQpuQKz+4mLfX88lyplJ7o2x3uoecFlIBT92oY6BI+yxTATnwSIK249OwX2vuG6HLSOXlI3RScHjEDtzz1zPK6kjCGkFBM+5+gMyTIF4C130Uk4rlXpxYOGwPoBKBD2buGbBh7AHIaFgHlYjShdPiHMCF0ZDQnOubk139cRzoCVkKqdpPgVqIy8q6982UXzeoSIxqpSauqaL0IK2PPbMy3s9WH12NrDFDkg4NLAhnEgGNfOPjMl9lRMW+i4cCw0jhaCx1bu5GTEQzDCpc0DFT+/KPkKc="
       }');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

$response = curl_exec($ch);

curl_close($ch);
```

{% endtab %}
{% endtabs %}

**响应示例**

**成功响应**

| 名称                    | 类型     | 说明                                                                                                                      |
| --------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------- |
| resultType            | string | 请求结果。成功时 `SUCCESS`，失败： `FAIL`)                                                                                          |
| success.txId          | string | 可作为认证请求事务 ID 唯一标识交易的值。由于是能够唯一标识特定交易的值，因此必须保存并管理。                                                                        |
| success.requestedDt   | string | 首次请求时间（`YYYY-MM-DDThh:mm:ss±hh:mm`)                                                                                     |
| success.appScheme     | string | 可打开 Toss 认证界面的 App Scheme 信息                                                                                            |
| success.androidAppUri | string | 作为 Android 认证 App Scheme 值，作用与 appScheme 相同，但由于使用 Chrome Intent，能够在无需客户公司额外功能实现的情况下判断是否安装了 Toss App，这是一大优点。             |
| success.iosAppUri     | string | 作为 iOS 认证 App Scheme 值，作用与 appScheme 相同，但由于使用 Universal Link，因此与 Android 一样，能够在无需客户公司额外功能实现的情况下判断是否安装了 Toss App，这是一大优点。 |

```json
{
  "resultType": "SUCCESS",
  "success": {
    "txId": "d7b7273b-407b-46be-a9d8-97d2e895b009",
    "appScheme": "null",
    "androidAppUri": "null",
    "iosAppUri": "null",
    "requestedDt": "2022-02-13T17:52:22+09:00"
  }
}
```

**失败响应**

| 名称              | 类型             | 说明                 |
| --------------- | -------------- | ------------------ |
| resultType      | string         | 失败时 `FAIL`         |
| error.errorType | number         | 错误类型               |
| error.errorCode | string         | 错误代码（例如： `CE1000`) |
| error.reason    | string         | 错误消息               |
| error.data      | object         | 附加数据（如有）           |
| error.title     | string \| null | 错误标题（如有）           |

```json
{
  "resultType": "FAIL",
  "error": {
    "errorType": 0,
    "errorCode": "CE1000",
    "reason": "令牌无效。",
    "data": {},
    "title": null
  },
  "success": null
}
```

{% hint style="info" %}
**下一步**

响应中的 `txId`使用 [appsInTossSignTossCert](#_3-인증-화면-호출하기) 该函数后，将启动 Toss App 认证界面。 [调用认证界面](#_3-인증-화면-호출하기)请参考。
{% endhint %}

#### 2-2. 一次认证

在客户端 **无需输入个人信息** 调用 Toss App **一次完成认证**。

**请求头**

| 名称            | 类型     | 必填 | 说明                      |
| ------------- | ------ | -- | ----------------------- |
| Authorization | string | Y  | `Bearer {Access Token}` |
| Content-Type  | string | Y  | `application/json`      |

**请求参数**

| 名称          | 类型     | 必填 | 说明                  |
| ----------- | ------ | -- | ------------------- |
| requestType | string | Y  | `"USER_NONE"`       |
| requestUrl  | string | Y  | 认证完成后返回的 App Scheme |

**请求示例**

{% tabs %}
{% tab title="Shell(curl)" %}

```bash
curl --location --request POST 'https://cert.toss.im/api/v2/sign/user/auth/request' --header 'Authorization: Bearer eyJraWQiOiJjZXJ0IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJ0ZXN0X2E4ZTIzMzM2ZDY3M2NhNzA5MjJiNDg1ZmU4MDZlYjJkIiwiYXVkIjoidGVzdF9hOGUyMzMzNmQ2NzNjYTcwOTIyYjQ4NWZlODA2ZWIyZCIsIm5iZiI6MTY0OTIyMjk3OCwic2NvcGUiOlsiY2EiXSwiaXNzIjoiaHR0cHM6XC9cL2NlcnQudG9zcy5pbSIsImV4cCI6MTY4MDc1ODk3OCwiaWF0IjoxNjQ5MjIyOTc4LCJqdGkiOiI4MDNjNDBjOC1iMzUxLTRmOGItYTIxNC1iNjc5MmNjMzBhYTcifQ.cjDZ0lAXbuf-KAgi3FlG1YGxvgvT3xrOYKDTstfbUz6CoNQgvd9TqI6RmsGZuona9jIP6H12Z1Xb07RIfAVoTK-J9iC5_Yp8ZDdcalsMNj51pPP8wso86rn-mKsrx1J5Rdi3GU58iKt0zGr4KzqSxUJkul9G4rY03KInwvl692HU19kYA9y8uTI4bBX--UPfQ02G0QH9HGTPHs7lZsISDtyD8sB2ikz5p7roua7U467xWy4BnRleCEWO2uUaNNGnwd7SvbjhmsRZqohs9KzDUsFjVhSiRNdHL53XJQ5zFHwDF92inRZFLu6Dw8xttPtNHwAD1kT84uXJcVMfEHtwkQ' --header 'Content-Type: application/json' # 会话密钥需在每次请求时重新生成。
--data-raw '{
       "requestType" : "USER_NONE",
       "requestUrl" : "intoss://my-granite-app",
    }'
```

{% endtab %}

{% tab title="java" %}

```java
URL url = new URL("https://cert.toss.im/api/v2/sign/user/auth/request");
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
httpConn.setRequestMethod("POST");
httpConn.setRequestProperty("Authorization", "Bearer eyJraWQiOiJjZXJ0IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJ0ZXN0X2E4ZTIzMzM2ZDY3M2NhNzA5MjJiNDg1ZmU4MDZlYjJkIiwiYXVkIjoidGVzdF9hOGUyMzMzNmQ2NzNjYTcwOTIyYjQ4NWZlODA2ZWIyZCIsIm5iZiI6MTY0OTIyMjk3OCwic2NvcGUiOlsiY2EiXSwiaXNzIjoiaHR0cHM6XC9cL2NlcnQudG9zcy5pbSIsImV4cCI6MTY4MDc1ODk3OCwiaWF0IjoxNjQ5MjIyOTc4LCJqdGkiOiI4MDNjNDBjOC1iMzUxLTRmOGItYTIxNC1iNjc5MmNjMzBhYTcifQ.cjDZ0lAXbuf-KAgi3FlG1YGxvgvT3xrOYKDTstfbUz6CoNQgvd9TqI6RmsGZuona9jIP6H12Z1Xb07RIfAVoTK-J9iC5_Yp8ZDdcalsMNj51pPP8wso86rn-mKsrx1J5Rdi3GU58iKt0zGr4KzqSxUJkul9G4rY03KInwvl692HU19kYA9y8uTI4bBX--UPfQ02G0QH9HGTPHs7lZsISDtyD8sB2ikz5p7roua7U467xWy4BnRleCEWO2uUaNNGnwd7SvbjhmsRZqohs9KzDUsFjVhSiRNdHL53XJQ5zFHwDF92inRZFLu6Dw8xttPtNHwAD1kT84uXJcVMfEHtwkQ");
httpConn.setRequestProperty("Content-Type", "application/json");
// 会话密钥需在每次请求时重新生成。
httpConn.setDoOutput(true);
OutputStreamWriter writer = new OutputStreamWriter(httpConn.getOutputStream());
writer.write("{" +
        "\"requestType\" : \"USER_NONE\"," +
        "}");
writer.flush();
writer.close();
httpConn.getOutputStream().close();
InputStream responseStream = httpConn.getResponseCode() == 200
        ? httpConn.getInputStream()
        : httpConn.getErrorStream();
Scanner s = new Scanner(responseStream).useDelimiter("\\A");
String response = s.hasNext() ? s.next() : "";
System.out.println(response);
```

{% endtab %}

{% tab title="php" %}

```php
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://cert.toss.im/api/v2/sign/user/auth/request');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');

curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer eyJraWQiOiJjZXJ0IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJ0ZXN0X2E4ZTIzMzM2ZDY3M2NhNzA5MjJiNDg1ZmU4MDZlYjJkIiwiYXVkIjoidGVzdF9hOGUyMzMzNmQ2NzNjYTcwOTIyYjQ4NWZlODA2ZWIyZCIsIm5iZiI6MTY0OTIyMjk3OCwic2NvcGUiOlsiY2EiXSwiaXNzIjoiaHR0cHM6XC9cL2NlcnQudG9zcy5pbSIsImV4cCI6MTY4MDc1ODk3OCwiaWF0IjoxNjQ5MjIyOTc4LCJqdGkiOiI4MDNjNDBjOC1iMzUxLTRmOGItYTIxNC1iNjc5MmNjMzBhYTcifQ.cjDZ0lAXbuf-KAgi3FlG1YGxvgvT3xrOYKDTstfbUz6CoNQgvd9TqI6RmsGZuona9jIP6H12Z1Xb07RIfAVoTK-J9iC5_Yp8ZDdcalsMNj51pPP8wso86rn-mKsrx1J5Rdi3GU58iKt0zGr4KzqSxUJkul9G4rY03KInwvl692HU19kYA9y8uTI4bBX--UPfQ02G0QH9HGTPHs7lZsISDtyD8sB2ikz5p7roua7U467xWy4BnRleCEWO2uUaNNGnwd7SvbjhmsRZqohs9KzDUsFjVhSiRNdHL53XJQ5zFHwDF92inRZFLu6Dw8xttPtNHwAD1kT84uXJcVMfEHtwkQ',
    'Content-Type: application/json',
]);

curl_setopt($ch, CURLOPT_POSTFIELDS, '{
    "requestType": "USER_NONE",
    "requestUrl": "intoss://my-granite-app"
}');

curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

$response = curl_exec($ch);

curl_close($ch);
```

{% endtab %}
{% endtabs %}

**响应示例**

**成功响应**

| 名称                  | 类型     | 说明                                               |
| ------------------- | ------ | ------------------------------------------------ |
| resultType          | string | 请求结果。成功时 `SUCCESS`，失败： `FAIL`)                   |
| success.txId        | string | 可作为认证请求事务 ID 唯一标识交易的值。由于是能够唯一标识特定交易的值，因此必须保存并管理。 |
| success.requestedDt | string | 首次请求时间（`YYYY-MM-DDThh:mm:ss±hh:mm`)              |

```json
{
  "resultType": "SUCCESS",
  "success": {
    "txId": "d7b7273b-407b-46be-a9d8-97d2e895b009",
    "requestedDt": "2022-02-13T17:52:22+09:00"
  }
}
```

**失败响应**

| 名称              | 类型             | 说明                 |
| --------------- | -------------- | ------------------ |
| resultType      | string         | 失败时 `FAIL`         |
| error.errorType | number         | 错误类型               |
| error.errorCode | string         | 错误代码（例如： `CE1000`) |
| error.reason    | string         | 错误消息               |
| error.data      | object         | 附加数据（如有）           |
| error.title     | string \| null | 错误标题（如有）           |

```json
{
  "resultType": "FAIL",
  "error": {
    "errorType": 0,
    "errorCode": "CE1000",
    "reason": "令牌无效。",
    "data": {},
    "title": null
  },
  "success": null
}
```

{% hint style="info" %}
**下一步**

响应中的 `txId`使用 [appsInTossSignTossCert](#_3-인증-화면-호출하기) 该函数后，将启动 Toss App 认证界面。 [调用认证界面](#_3-인증-화면-호출하기)请参考。
{% endhint %}

### 3. 调用认证界面

将从身份验证请求 API 响应中获得的 `txId`包含 `appsInTossSignTossCert`后调用，即可启动 Toss App 认证界面。

{% hint style="info" %}
**一次认证及应用版本说明**

**一次认证方式（`USER_NONE`)** 在使用时， `skipConfirmDoc`将 `true`设置为该值后，可跳过认证书确认文档步骤。

* Toss 认证（requestType: USER\_PERSONAL）：Toss App 5.233.0 以上
* Toss 一次认证（requestType: USER\_NONE）：Toss App 5.236.0 以上

请使用 getTossAppVersion 函数检查 Toss App 版本。
{% endhint %}

{% tabs %}
{% tab title="js" %}

```js
import { appsInTossSignTossCert } from '@apps-in-toss/web-framework';

/**
 * 将基于 txId 调用 Toss 认证书界面。
 *
 * 参考：
 * response 并非用于确认认证完成。tosscertRequest
 * 请在服务器端以 txId 为准调用结果查询 API，判定最终状态。
 */
async function openTossCertWithTxId({ txId, skipConfirmDoc = false }) {
  try {
    const params = { txId, skipConfirmDoc };
    const response = await appsInTossSignTossCert(params);
    return response;
  } catch (error) {
    // 调用失败处理（用户取消/未安装应用/Schema 失败等）
    console.error(error);
  }
}
```

{% endtab %}

{% tab title="React" %}

```tsx
import { appsInTossSignTossCert } from '@apps-in-toss/web-framework';

interface AppsInTossSignTossCertParams {
  txId: string; // 身份验证请求时发放的 txId
  skipConfirmDoc?: boolean; // 一次认证时设为 true
}

/**
 * 将基于 txId 调用 Toss 认证书界面。
 *
 * 参考：
 * response 并非用于确认认证完成。
 * 请在服务器端以 txId 为准调用结果查询 API，判定最终状态。
 */
async function openTossCertWithTxId(txId: string, skipConfirmDoc = false): Promise<unknown> {
  try {
    const params: AppsInTossSignTossCertParams = { txId, skipConfirmDoc };
    const response = await appsInTossSignTossCert(params);
    return response;
  } catch (error: unknown) {
    // 调用失败处理（用户取消/未安装应用/Schema 失败等）
    console.error(error);
  }
}
```

{% endtab %}

{% tab title="React Native" %}

```tsx
import { appsInTossSignTossCert } from '@apps-in-toss/framework';

interface AppsInTossSignTossCertParams {
  txId: string; // 身份验证请求时发放的 txId
  skipConfirmDoc?: boolean; // 一次认证时设为 true
}

/**
 * 将基于 txId 调用 Toss 认证书界面。
 *
 * 参考：
 * response 并非用于确认认证完成。
 * 请在服务器端以 txId 为准调用结果查询 API，判定最终状态。
 */
async function openTossCertWithTxId(txId: string, skipConfirmDoc = false): Promise<unknown> {
  try {
    const params: AppsInTossSignTossCertParams = { txId, skipConfirmDoc };
    const response = await appsInTossSignTossCert(params);
    return response;
  } catch (error: unknown) {
    // 调用失败处理（用户取消/未安装应用/Schema 失败等）
    console.error(error);
  }
}
```

{% endtab %}
{% endtabs %}

**响应**

* `onSuccess`
  * 无参数
* `onError`
  * `Error { code: string; message: string }` (例如：用户取消、未安装应用、Schema 失败等)

```ts
// 错误类型示例
type AppsInTossSignTossCertError = {
  code: string;
  message: string;
};

// 通过 try/catch 处理 onSuccess/onError
try {
  await appsInTossSignTossCert({
    params: {
      txId: 'bb8bead6-0957-4be7-b937-f554911d7a87',
      skipConfirmDoc: true, // 一次认证时设置
    },
  });
  // onSuccess：无参数
} catch (e: any) {
  const err: AppsInTossSignTossCertError = {
    code: e?.code ?? 'UNKNOWN',
    message: e?.message ?? String(e),
  };
  // onError：错误处理
}
```

### 4. 查询身份验证状态

用户当前认证 **进行状态**进行查询。 `txId`使用其可确认当前认证阶段（`REQUESTED`, `IN_PROGRESS`, `COMPLETED`, `EXPIRED`）可确认。

{% hint style="info" %}
**请注意**

状态查询 API 是 **用于确认进行状态**。最终认证是否成功需通过 **结果查询 API**来判断。
{% endhint %}

* BaseURL : `https://cert.toss.im`
* Endpoint : `/api/v2/sign/user/auth/id/status`
* Method : `POST`
* Content-type : `application/json`

**请求头**

| 名称            | 类型     | 是否必填 | 说明                      |
| ------------- | ------ | ---- | ----------------------- |
| Authorization | string | Y    | `Bearer {Access Token}` |
| Content-Type  | string | Y    | `application/json`      |

**请求参数**

| 名称   | 类型     | 是否必填 | 说明               |
| ---- | ------ | ---- | ---------------- |
| txId | string | Y    | 需要确认状态的认证请求事务 ID |

**请求示例**

{% tabs %}
{% tab title="Shell(curl)" %}

```bash
curl --location --request POST 'https://cert.toss.im/api/v2/sign/user/auth/id/status' \\
--header 'Authorization: Bearer eyJraWQiOiJjZXJ0IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJ0ZXN0X2E4ZTIzMzM2ZDY3M2NhNzA5MjJiNDg1ZmU4MDZlYjJkIiwiYXVkIjoidGVzdF9hOGUyMzMzNmQ2NzNjYTcwOTIyYjQ4NWZlODA2ZWIyZCIsIm5iZiI6MTY0OTIyMjk3OCwic2NvcGUiOlsiY2EiXSwiaXNzIjoiaHR0cHM6XC9cL2NlcnQudG9zcy5pbSIsImV4cCI6MTY4MDc1ODk3OCwiaWF0IjoxNjQ5MjIyOTc4LCJqdGkiOiI4MDNjNDBjOC1iMzUxLTRmOGItYTIxNC1iNjc5MmNjMzBhYTcifQ.cjDZ0lAXbuf-KAgi3FlG1YGxvgvT3xrOYKDTstfbUz6CoNQgvd9TqI6RmsGZuona9jIP6H12Z1Xb07RIfAVoTK-J9iC5_Yp8ZDdcalsMNj51pPP8wso86rn-mKsrx1J5Rdi3GU58iKt0zGr4KzqSxUJkul9G4rY03KInwvl692HU19kYA9y8uTI4bBX--UPfQ02G0QH9HGTPHs7lZsISDtyD8sB2ikz5p7roua7U467xWy4BnRleCEWO2uUaNNGnwd7SvbjhmsRZqohs9KzDUsFjVhSiRNdHL53XJQ5zFHwDF92inRZFLu6Dw8xttPtNHwAD1kT84uXJcVMfEHtwkQ' \\
--header 'Content-Type: application/json' \
--data-raw '{
      "txId": "633f3e1b-1a11-4e7c-9b35-dd391f440be4"
    }'
```

{% endtab %}

{% tab title="java" %}

```java
URL url = new URL("https://cert.toss.im/api/v2/sign/user/auth/id/status");
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
httpConn.setRequestMethod("POST");

httpConn.setRequestProperty("Authorization", "Bearer eyJraWQiOiJjZXJ0IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJ0ZXN0X2E4ZTIzMzM2ZDY3M2NhNzA5MjJiNDg1ZmU4MDZlYjJkIiwiYXVkIjoidGVzdF9hOGUyMzMzNmQ2NzNjYTcwOTIyYjQ4NWZlODA2ZWIyZCIsIm5iZiI6MTY0OTIyMjk3OCwic2NvcGUiOlsiY2EiXSwiaXNzIjoiaHR0cHM6XC9cL2NlcnQudG9zcy5pbSIsImV4cCI6MTY4MDc1ODk3OCwiaWF0IjoxNjQ5MjIyOTc4LCJqdGkiOiI4MDNjNDBjOC1iMzUxLTRmOGItYTIxNC1iNjc5MmNjMzBhYTcifQ.cjDZ0lAXbuf-KAgi3FlG1YGxvgvT3xrOYKDTstfbUz6CoNQgvd9TqI6RmsGZuona9jIP6H12Z1Xb07RIfAVoTK-J9iC5_Yp8ZDdcalsMNj51pPP8wso86rn-mKsrx1J5Rdi3GU58iKt0zGr4KzqSxUJkul9G4rY03KInwvl692HU19kYA9y8uTI4bBX--UPfQ02G0QH9HGTPHs7lZsISDtyD8sB2ikz5p7roua7U467xWy4BnRleCEWO2uUaNNGnwd7SvbjhmsRZqohs9KzDUsFjVhSiRNdHL53XJQ5zFHwDF92inRZFLu6Dw8xttPtNHwAD1kT84uXJcVMfEHtwkQ");
httpConn.setRequestProperty("Content-Type", "application/json");
httpConn.setDoOutput(true);
OutputStreamWriter writer = new OutputStreamWriter(httpConn.getOutputStream());
writer.write("{\"txId\": \"633f3e1b-1a11-4e7c-9b35-dd391f440be4\"}");
writer.flush();
writer.close();

httpConn.getOutputStream().close();
InputStream responseStream = httpConn.getResponseCode() == 200
        ? httpConn.getInputStream()
        : httpConn.getErrorStream();
Scanner s = new Scanner(responseStream).useDelimiter("\\A");
String response = s.hasNext() ? s.next() : "";
System.out.println(response);
```

{% endtab %}

{% tab title="php" %}

```php
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://cert.toss.im/api/v2/sign/user/auth/id/status');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization' : 'Bearer eyJraWQiOiJjZXJ0IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJ0ZXN0X2E4ZTIzMzM2ZDY3M2NhNzA5MjJiNDg1ZmU4MDZlYjJkIiwiYXVkIjoidGVzdF9hOGUyMzMzNmQ2NzNjYTcwOTIyYjQ4NWZlODA2ZWIyZCIsIm5iZiI6MTY0OTIyMjk3OCwic2NvcGUiOlsiY2EiXSwiaXNzIjoiaHR0cHM6XC9cL2NlcnQudG9zcy5pbSIsImV4cCI6MTY4MDc1ODk3OCwiaWF0IjoxNjQ5MjIyOTc4LCJqdGkiOiI4MDNjNDBjOC1iMzUxLTRmOGItYTIxNC1iNjc5MmNjMzBhYTcifQ.cjDZ0lAXbuf-KAgi3FlG1YGxvgvT3xrOYKDTstfbUz6CoNQgvd9TqI6RmsGZuona9jIP6H12Z1Xb07RIfAVoTK-J9iC5_Yp8ZDdcalsMNj51pPP8wso86rn-mKsrx1J5Rdi3GU58iKt0zGr4KzqSxUJkul9G4rY03KInwvl692HU19kYA9y8uTI4bBX--UPfQ02G0QH9HGTPHs7lZsISDtyD8sB2ikz5p7roua7U467xWy4BnRleCEWO2uUaNNGnwd7SvbjhmsRZqohs9KzDUsFjVhSiRNdHL53XJQ5zFHwDF92inRZFLu6Dw8xttPtNHwAD1kT84uXJcVMfEHtwkQ',
    'Content-Type' : 'application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"txId": "633f3e1b-1a11-4e7c-9b35-dd391f440be4"}');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

$response = curl_exec($ch);

curl_close($ch);
```

{% endtab %}
{% endtabs %}

**成功响应**

| 名称                  | 类型     | 说明                                             |
| ------------------- | ------ | ---------------------------------------------- |
| resultType          | string | 请求结果。成功时 `SUCCESS`                             |
| success.txId        | string | 查询到的认证事务 ID                                    |
| success.status      | string | 认证进行状态（参考下方“status 值”表）                        |
| success.requestedDt | string | 首次认证请求时间（`YYYY-MM-DDThh:mm:ss±hh:mm`，ISO 8601） |

```json
{
  "resultType": "SUCCESS",
  "success": {
    "txId": "633f3e1b-1a11-4e7c-9b35-dd391f440be4",
    "status": "REQUESTED",
    "requestedDt": "2022-02-13T18:00:26+09:00"
  }
}
```

**失败响应**

| 名称              | 类型             | 说明                 |
| --------------- | -------------- | ------------------ |
| resultType      | string         | 失败时 `FAIL`         |
| error.errorType | number         | 错误类型               |
| error.errorCode | string         | 错误代码（例如： `CE3100`) |
| error.reason    | string         | 错误消息               |
| error.data      | object         | 附加数据（如有）           |
| error.title     | string \| null | 错误标题（如有）           |

```json
{
  "resultType": "FAIL",
  "error": {
    "errorType": 0,
    "errorCode": "CE3100",
    "reason": "不存在的请求",
    "data": {},
    "title": null
  },
  "success": null
}
```

**status 值**

| 值            | 说明                                  |
| ------------ | ----------------------------------- |
| REQUESTED    | 在 Toss 认证服务器向用户的 Toss App 发起认证请求的状态 |
| IN\_PROGRESS | 用户正在进行认证的状态                         |
| COMPLETED    | 客户已完成认证的状态 *(最终确认需通过结果查询 API 判断)*   |
| EXPIRED      | 因有效期已过，无法继续认证的状态                    |

### 5. 查询身份验证结果

已完成认证的用户的 **结果信息**进行查询。查询必须 **通过服务器-服务器通信**进行。请将身份验证结果中收集到的信息安全地存储在服务器上，并在之后进行电子签名/便捷认证时与该信息进行比较和验证。

{% hint style="info" %}
**请注意**

结果查询 API 以成功为准 **最多 2 次**只能查询到此为止。用户认证完成后 **60分钟（1小时）内** 需要结束结果查询。超过60分钟后，结果查询将受限，并且需要从认证请求 API 重新开始。
{% endhint %}

* BaseURL : `https://cert.toss.im`
* Endpoint : `/api/v2/sign/user/auth/id/result`
* Method : `POST`
* Content-type : `application/json`

**请求头**

| 名称            | 类型     | 是否必填 | 说明                      |
| ------------- | ------ | ---- | ----------------------- |
| Authorization | string | Y    | `Bearer {Access Token}` |
| Content-Type  | string | Y    | `application/json`      |

**请求参数**

| 名称         | 类型     | 是否必填 | 说明                                                                                            |
| ---------- | ------ | ---- | --------------------------------------------------------------------------------------------- |
| txId       | string | Y    | 需要确认结果的认证请求事务 ID                                                                              |
| sessionKey | string | Y    | 在结果查询中，不论认证方式 `txId`需一并必传。作为请求/响应 AES 加解密用的会话密钥，每次请求都要重新生成，认证请求中使用的会话密钥禁止复用 [（生成方法）](#세션키-생성) |

**请求示例**

{% tabs %}
{% tab title="Shell(curl)" %}

```bash
curl --location --request POST 'https://cert.toss.im/api/v2/sign/user/auth/id/result' \\
--header 'Authorization: Bearer eyJraWQiOiJjZXJ0IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJ0ZXN0X2E4ZTIzMzM2ZDY3M2NhNzA5MjJiNDg1ZmU4MDZlYjJkIiwiYXVkIjoidGVzdF9hOGUyMzMzNmQ2NzNjYTcwOTIyYjQ4NWZlODA2ZWIyZCIsIm5iZiI6MTY0OTIyMjk3OCwic2NvcGUiOlsiY2EiXSwiaXNzIjoiaHR0cHM6XC9cL2NlcnQudG9zcy5pbSIsImV4cCI6MTY4MDc1ODk3OCwiaWF0IjoxNjQ5MjIyOTc4LCJqdGkiOiI4MDNjNDBjOC1iMzUxLTRmOGItYTIxNC1iNjc5MmNjMzBhYTcifQ.cjDZ0lAXbuf-KAgi3FlG1YGxvgvT3xrOYKDTstfbUz6CoNQgvd9TqI6RmsGZuona9jIP6H12Z1Xb07RIfAVoTK-J9iC5_Yp8ZDdcalsMNj51pPP8wso86rn-mKsrx1J5Rdi3GU58iKt0zGr4KzqSxUJkul9G4rY03KInwvl692HU19kYA9y8uTI4bBX--UPfQ02G0QH9HGTPHs7lZsISDtyD8sB2ikz5p7roua7U467xWy4BnRleCEWO2uUaNNGnwd7SvbjhmsRZqohs9KzDUsFjVhSiRNdHL53XJQ5zFHwDF92inRZFLu6Dw8xttPtNHwAD1kT84uXJcVMfEHtwkQ' \\
--header 'Content-Type: application/json' \
# 会话密钥需要在每次请求时重新生成。
--data-raw '{
       "txId" : "c1ce9214-9878-4751-b433-0c96641b0e13",
       "sessionKey" : "v1$71c3d6cd-6a74-48a8-8ab2-b48e6133ae6f$Q0U7Bdg4dWd0XXucjsM/mda89bFU7eHnoUhgQ3k+cGQ9gv37jvWC+8isrkO2CR4+qgoPg+U+K7/tQH2m+uU7L8Ab0gzbQo6ASX39NpcP6RHpI+VBi323ssYnBmJL7n0z4aNm6raUEsMoNwrOaMDe0DqfalgOeZgZUztWew1pfZul2Q3/WIBMdp+npS4sFnBRoBrzLroVsuNRTLK0XT6m5hak+ys+vBg5vZFoI0JN7j7zsr8lqGi6piSkygl1PLPugnSC9cOezxMoVN5c/csEVQxMsfkwqTIASaZVECnP50dO70TydYhBFCqxw3DpEDBHcXNDucOtdVOPslCPNx3NZv1i0IH0r92ULb3w2Y0Fncy4/xL1dPSS+TbA5540u2Wb3cxqVNHib7WwSMHBwQtXAnFSFZmcvQQPXtTeQ7SCvNnhA8k3gbboSpbDBg60RWn/1zF/ogBYRldO1BFtq7KP+jOm6I2OSSVpagH1Wu5MXhEtiTmsx7M8j/IM8EfnXbD9axJnlW2fKHZVvAj+5KNhqy90PUimBCKiXqjvUwOqb9hGGEzJ4JVKbIIiy1EYOaRkPTK9GurZwQaqM4o4c8pzOYRQR/3XIPWHxLv/jwsaMcfUIQFyKE+w898g+l1zO0jcck59/R64kZcirT9AsGFnRUWrsHGIkM95jdYlpUsnCXw="
  }'
```

{% endtab %}

{% tab title="java" %}

```java
URL url = new URL("https://cert.toss.im/api/v2/sign/user/auth/id/result");
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
httpConn.setRequestMethod("POST");

httpConn.setRequestProperty("Authorization", "Bearer eyJraWQiOiJjZXJ0IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJ0ZXN0X2E4ZTIzMzM2ZDY3M2NhNzA5MjJiNDg1ZmU4MDZlYjJkIiwiYXVkIjoidGVzdF9hOGUyMzMzNmQ2NzNjYTcwOTIyYjQ4NWZlODA2ZWIyZCIsIm5iZiI6MTY0OTIyMjk3OCwic2NvcGUiOlsiY2EiXSwiaXNzIjoiaHR0cHM6XC9cL2NlcnQudG9zcy5pbSIsImV4cCI6MTY4MDc1ODk3OCwiaWF0IjoxNjQ5MjIyOTc4LCJqdGkiOiI4MDNjNDBjOC1iMzUxLTRmOGItYTIxNC1iNjc5MmNjMzBhYTcifQ.cjDZ0lAXbuf-KAgi3FlG1YGxvgvT3xrOYKDTstfbUz6CoNQgvd9TqI6RmsGZuona9jIP6H12Z1Xb07RIfAVoTK-J9iC5_Yp8ZDdcalsMNj51pPP8wso86rn-mKsrx1J5Rdi3GU58iKt0zGr4KzqSxUJkul9G4rY03KInwvl692HU19kYA9y8uTI4bBX--UPfQ02G0QH9HGTPHs7lZsISDtyD8sB2ikz5p7roua7U467xWy4BnRleCEWO2uUaNNGnwd7SvbjhmsRZqohs9KzDUsFjVhSiRNdHL53XJQ5zFHwDF92inRZFLu6Dw8xttPtNHwAD1kT84uXJcVMfEHtwkQ");
httpConn.setRequestProperty("Content-Type", "application/json");

// 会话密钥需在每次请求时重新生成。
httpConn.setDoOutput(true);
OutputStreamWriter writer = new OutputStreamWriter(httpConn.getOutputStream());
writer.write("{" +
        "\"txId\" : \"c1ce9214-9878-4751-b433-0c96641b0e13\"," +
        "\"sessionKey\" : \"v1$71c3d6cd-6a74-48a8-8ab2-b48e6133ae6f$Q0U7Bdg4dWd0XXucjsM/mda89bFU7eHnoUhgQ3k+cGQ9gv37jvWC+8isrkO2CR4+qgoPg+U+K7/tQH2m+uU7L8Ab0gzbQo6ASX39NpcP6RHpI+VBi323ssYnBmJL7n0z4aNm6raUEsMoNwrOaMDe0DqfalgOeZgZUztWew1pfZul2Q3/WIBMdp+npS4sFnBRoBrzLroVsuNRTLK0XT6m5hak+ys+vBg5vZFoI0JN7j7zsr8lqGi6piSkygl1PLPugnSC9cOezxMoVN5c/csEVQxMsfkwqTIASaZVECnP50dO70TydYhBFCqxw3DpEDBHcXNDucOtdVOPslCPNx3NZv1i0IH0r92ULb3w2Y0Fncy4/xL1dPSS+TbA5540u2Wb3cxqVNHib7WwSMHBwQtXAnFSFZmcvQQPXtTeQ7SCvNnhA8k3gbboSpbDBg60RWn/1zF/ogBYRldO1BFtq7KP+jOm6I2OSSVpagH1Wu5MXhEtiTmsx7M8j/IM8EfnXbD9axJnlW2fKHZVvAj+5KNhqy90PUimBCKiXqjvUwOqb9hGGEzJ4JVKbIIiy1EYOaRkPTK9GurZwQaqM4o4c8pzOYRQR/3XIPWHxLv/jwsaMcfUIQFyKE+w898g+l1zO0jcck59/R64kZcirT9AsGFnRUWrsHGIkM95jdYlpUsnCXw=\"" +
        "}");
writer.flush();
writer.close();

httpConn.getOutputStream().close();
InputStream responseStream = httpConn.getResponseCode() / 100 == 2
        ? httpConn.getInputStream()
        : httpConn.getErrorStream();
Scanner s = new Scanner(responseStream).useDelimiter("\\A");
String response = s.hasNext() ? s.next() : "";
System.out.println(response);
```

{% endtab %}

{% tab title="php" %}

```php
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://cert.toss.im/api/v2/sign/user/auth/id/result');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization' : 'Bearer eyJraWQiOiJjZXJ0IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJ0ZXN0X2E4ZTIzMzM2ZDY3M2NhNzA5MjJiNDg1ZmU4MDZlYjJkIiwiYXVkIjoidGVzdF9hOGUyMzMzNmQ2NzNjYTcwOTIyYjQ4NWZlODA2ZWIyZCIsIm5iZiI6MTY0OTIyMjk3OCwic2NvcGUiOlsiY2EiXSwiaXNzIjoiaHR0cHM6XC9cL2NlcnQudG9zcy5pbSIsImV4cCI6MTY4MDc1ODk3OCwiaWF0IjoxNjQ5MjIyOTc4LCJqdGkiOiI4MDNjNDBjOC1iMzUxLTRmOGItYTIxNC1iNjc5MmNjMzBhYTcifQ.cjDZ0lAXbuf-KAgi3FlG1YGxvgvT3xrOYKDTstfbUz6CoNQgvd9TqI6RmsGZuona9jIP6H12Z1Xb07RIfAVoTK-J9iC5_Yp8ZDdcalsMNj51pPP8wso86rn-mKsrx1J5Rdi3GU58iKt0zGr4KzqSxUJkul9G4rY03KInwvl692HU19kYA9y8uTI4bBX--UPfQ02G0QH9HGTPHs7lZsISDtyD8sB2ikz5p7roua7U467xWy4BnRleCEWO2uUaNNGnwd7SvbjhmsRZqohs9KzDUsFjVhSiRNdHL53XJQ5zFHwDF92inRZFLu6Dw8xttPtNHwAD1kT84uXJcVMfEHtwkQ',
    'Content-Type' : 'application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{
       "txId" : "c1ce9214-9878-4751-b433-0c96641b0e13",
       "sessionKey" : "v1$71c3d6cd-6a74-48a8-8ab2-b48e6133ae6f$Q0U7Bdg4dWd0XXucjsM/mda89bFU7eHnoUhgQ3k+cGQ9gv37jvWC+8isrkO2CR4+qgoPg+U+K7/tQH2m+uU7L8Ab0gzbQo6ASX39NpcP6RHpI+VBi323ssYnBmJL7n0z4aNm6raUEsMoNwrOaMDe0DqfalgOeZgZUztWew1pfZul2Q3/WIBMdp+npS4sFnBRoBrzLroVsuNRTLK0XT6m5hak+ys+vBg5vZFoI0JN7j7zsr8lqGi6piSkygl1PLPugnSC9cOezxMoVN5c/csEVQxMsfkwqTIASaZVECnP50dO70TydYhBFCqxw3DpEDBHcXNDucOtdVOPslCPNx3NZv1i0IH0r92ULb3w2Y0Fncy4/xL1dPSS+TbA5540u2Wb3cxqVNHib7WwSMHBwQtXAnFSFZmcvQQPXtTeQ7SCvNnhA8k3gbboSpbDBg60RWn/1zF/ogBYRldO1BFtq7KP+jOm6I2OSSVpagH1Wu5MXhEtiTmsx7M8j/IM8EfnXbD9axJnlW2fKHZVvAj+5KNhqy90PUimBCKiXqjvUwOqb9hGGEzJ4JVKbIIiy1EYOaRkPTK9GurZwQaqM4o4c8pzOYRQR/3XIPWHxLv/jwsaMcfUIQFyKE+w898g+l1zO0jcck59/R64kZcirT9AsGFnRUWrsHGIkM95jdYlpUsnCXw="
       }');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

$response = curl_exec($ch);

curl_close($ch);
```

{% endtab %}
{% endtabs %}

**成功响应**

| 名称                     | 类型             | 说明                                                 |
| ---------------------- | -------------- | -------------------------------------------------- |
| resultType             | string         | 成功时 `SUCCESS`                                      |
| success.txId           | string         | 已查询结果的认证事务 ID                                      |
| success.status         | string         | `COMPLETED` *(结果查询已正常处理的状态)*                       |
| success.userIdentifier | string \| null | 当前版本未使用 (`null`)                                   |
| success.userCiToken    | string \| null | 当前版本未使用 (`null`)                                   |
| success.signature      | string         | 用户签名的电子签名值（**Base64 编码的 DER**). **需与 txId 一并保存管理** |
| success.randomValue    | string \| null | 当前版本未使用 (`null`)                                   |
| success.completedDt    | string         | 用户认证完成时间（`YYYY-MM-DDThh:mm:ss±hh:mm`，ISO 8601）     |
| success.requestedDt    | string         | 首次认证请求时间（`YYYY-MM-DDThh:mm:ss±hh:mm`，ISO 8601）     |
| success.personalData   | object         | 用于认证的 **个人信息（加密值）**. 请参考下级字段表                      |

**personalData（执行认证的用户个人信息）对象**

| 名称          | 类型             | 说明                                |
| ----------- | -------------- | --------------------------------- |
| ci          | string         | 加密后的用户 CI                         |
| name        | string         | 加密后的用户姓名                          |
| birthday    | string         | 加密后的8位出生日期                        |
| gender      | string         | 加密后的性别信息（`MALE` \| `FEMALE`)      |
| nationality | string         | 加密后的国籍（`LOCAL` \| `FOREIGNER`)    |
| ci2         | string \| null | 用于应对不可预测情况下 CI 泄露的临时参数， `null` 固定 |
| di          | string         | 加密后的用户 DI                         |
| ciUpdate    | string \| null | 用于应对不可预测情况下 CI 泄露的临时参数， `null` 固定 |
| ageGroup    | string         | 加密后的是否成人（`ADULT` \| `MINOR`)      |

```json
// 在结果查询响应中，无论调用认证的方式如何，都会提供相同的 body 参数。
{
  "resultType": "SUCCESS",
  "success": {
    "txId": "c1ce9214-9878-4751-b433-0c96641b0e13",
    "status": "COMPLETED",
    "userIdentifier": null,
    "userCiToken": null,
    "signature": "MIIJCAYJKoZIhvcN...(省略)...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",
    "randomValue": null,
    "completedDt": "2022-02-13T18:01:53+09:00",
    "requestedDt": "2022-02-13T18:00:26+09:00",
    "personalData": {
      "ci": "v1$b88f8717-8e76-4276-bed0-f769a8baf7be$X3g52aAyCBirz0UVp1oNRq0SfGtj66vGtUT3rp1aSdm1h//xmpm7vdf48fbGI2i7VTBj6TKG2rqanP6Yo9MiTQu63C8kLWayzWAMp+RLyXLovvnFb9SxxdblRtZbj5KRNlBWK9t2VXI=",
      "name": "v1$b88f8717-8e76-4276-bed0-f769a8baf7be$9oiJBRei1KI/SgXtXGmkfNHu+pdAUHXBxA==",
      "birthday": "v1$b88f8717-8e76-4276-bed0-f769a8baf7be$LQgw26ExChwWi8cQQz6GrdMAdMZGyaEI",
      "gender": "v1$b88f8717-8e76-4276-bed0-f769a8baf7be$WnREqd1HM/Ci7p+3KIqROusVkYeSAQ==",
      "nationality": "v1$b88f8717-8e76-4276-bed0-f769a8baf7be$UH5Kqd3dPV1daxw0i23eMWjeXcXC",
      "ci2": null,
      "di": "v1$2e161d9d-e620-443e-9a27-8db41cc96cf9$6GKr2zaUWWfI6rpJ6/AV9U4W0S4nhAMFIFLkt5CS6N8Gjb1Oc/dpitkMSSvLroDO5b6zdl9bufGSQ6SiVQdlYN2OWYFBr/Hb4e4AYwQpFxDbpi9ksYt52aFa3G2DwaNOQMUBkyQ1IWc=",
      "ciUpdate": null
    }
  }
}
```

**失败响应**

| 名称              | 类型             | 说明                 |
| --------------- | -------------- | ------------------ |
| resultType      | string         | 失败时 `FAIL`         |
| error.errorType | number         | 错误类型               |
| error.errorCode | string         | 错误代码（例如： `CE3102`) |
| error.reason    | string         | 错误消息               |
| error.data      | object         | 附加数据（如有）           |
| error.title     | string \| null | 错误标题（如有）           |

```json
{
  "resultType": "FAIL",
  "error": {
    "errorType": 0,
    "errorCode": "CE3102",
    "reason": "请求尚未完成。",
    "data": {},
    "title": null
  },
  "success": null
}
```

***

### 测试

即使合同尚未完成 **Toss认证测试环境**中可以尝试进行认证联动。请先完成联动后再进行测试。测试时请使用 **在应用商店安装的最新版本 Toss 应用**进行使用。 **身份验证**与 **一键认证** 两种方式都可以测试。

{% hint style="info" %}
**测试环境凭证**

* client\_id : `test_a8e23336d673ca70922b485fe806eb2d`
* client\_secret : `test_418087247d66da09fda1964dc4734e453c7cf66a7a9e3`
  {% endhint %}

**与生产环境的差异**

**认证使用费免费** — 即使认证成功完成，也不会收费。

**测试环境凭证** — `client_id`, `client_secret` 全部 `test_` 开头。可通过这个前缀轻松与生产环境信息区分。

**Access Token 有效期** — 为了方便联动 **1年（31536000秒）** 会应用有效期。在生产环境中，可能会根据商家申请的网络方式而有所不同。

**提供虚拟个人信息** — 不提供已注册 Toss 用户的加密个人信息，而是 **Toss 生成的虚拟人物的固定个人信息**会被传递。这是为了保护真实用户信息的措施；如果需要准确的用户信息， **从 Toss 获得的使用机构专属密钥**来与生产环境联动。

{% hint style="info" %}
**测试环境中提供的虚拟个人信息示例**

* CI : `CI0110000000001 ...`
* DI : `DI0110000000001 ...`
* 姓名：金Toss
* 出生日期：19930324
* 性别：FEMALE
* 本国/外国人：LOCAL
  {% endhint %}

***

### 会话密钥生成

为了安全起见，会话密钥（`sessionKey`）请在每次请求时重新生成。

{% tabs %}
{% tab title="java" %}

```java
public static String generateSessionKey(String sessionId, String secretKey, String iv, String base64PublicKey) throws Exception {
    String sessionAesKey = "AES_GCM$" + secretKey + "$" + iv;
    String encryptedSessionAesKey = encryptSessionAesKey(base64PublicKey, sessionAesKey);
    return "v1$" + sessionId + "$" + encryptedSessionAesKey;
}
```

{% endtab %}

{% tab title="javascript" %}

```javascript
generateSessionKey: function (sessionId, secretKey, iv, base64PublicKey) {
    let sessionAesKey = 'AES_GCM$' + secretKey + '$' + iv
    let encryptedSessionAesKey = this.encryptSessionAesKey(base64PublicKey, sessionAesKey)
    return 'v1$' + sessionId + '$' + encryptedSessionAesKey
},
```

{% endtab %}

{% tab title="csharp" %}

```csharp
public static string GenerateSessionKey(string sessionId, string secretKey, string iv, string base64PublicKey)
{
    string sessionAesKey = "AES_GCM$" + secretKey + "$" + iv;
    string encryptedSessionAesKey = EncryptSessionAesKey(base64PublicKey, sessionAesKey);
    return "v1$" + sessionId + "$" + encryptedSessionAesKey;
}
```

{% endtab %}

{% tab title="php" %}

```php
function generateSessionKey(string $sessionId, string $secretKey, string $iv, string $base64PublicKey): string
{
    $sessionAesKey = "AES_GCM$" . $secretKey . "$" . $iv;
    $encryptedSessionAesKey = encryptSessionAesKey($base64PublicKey, $sessionAesKey);
    return "v1$" . $sessionId . "$" . $encryptedSessionAesKey;
}
```

{% endtab %}

{% tab title="python" %}

```python
def generate_session_key(session_id, secret_key, iv, base64_public_key):
    session_aes_key = 'AES_GCM$' + secret_key + '$' + iv
    encrypted_session_aes_key = encrypt_session_aes_key(base64_public_key, session_aes_key)
    return 'v1$' + session_id + '$' + encrypted_session_aes_key
```

{% endtab %}

{% tab title="ruby" %}

```ruby
def generate_session_key(session_id, secret_key, iv, base64_public_key)
  session_aes_key = 'AES_GCM$' + secret_key + '$' + iv
  encrypted_session_aes_key = encrypt_session_aes_key(base64_public_key, session_aes_key)
  'v1$' + session_id + '$' + encrypted_session_aes_key
end
```

{% endtab %}
{% endtabs %}

更详细的示例请 [这里](https://github.com/toss/toss-cert-examples)查看。

***

### 个人信息加解密

Toss 认证 API 在部分请求中可能包含客户的个人信息。为确保安全，客户公司的服务器与 Toss 服务器只交换加密数据。如需明文，请解密数据后确认。

* 在认证请求中传递客户姓名、出生日期、手机号码时进行加密
* 当电子签名服务原文包含客户个人信息时，对原文进行加密
* 当认证结果中由 Toss 服务器提供包含 CI·DI 等的个人信息时进行加密

{% hint style="info" %}
**一键身份认证**

由于客户公司服务器不会将客户信息传递给 Toss认证服务器，因此不需要加密过程。不过，在用户认证完成后调用结果查询 API 时，需要携带会话密钥发起请求。
{% endhint %}

**会话密钥生成及加密示例**

{% tabs %}
{% tab title="java" %}

```java
package im.toss.cert.sdk;

import org.junit.jupiter.api.Test;

class TossCertSessionTest {

    @Test
    public void test() {

        // 1. 请先预先创建一次会话生成器。
        TossCertSessionGenerator tossCertSessionGenerator = new TossCertSessionGenerator();

        // 2. 在调用包含个人信息的认证请求 API 之前，请先生成会话。
        TossCertSession requestSession = tossCertSessionGenerator.generate();

        // 3. 请对个人信息进行加密。
        String userName = "金Toss";
        String encryptedUserName = requestSession.encrypt(userName);
        System.out.println("encryptedUserName: " + encryptedUserName);

        // 4. 请调用认证请求 API。
        // 请在认证请求 API 的 body 参数中添加生成的 sessionKey 和加密后的个人信息。
        String requestSessionKey = requestSession.getSessionKey();
        // requestCertification({
        //     "sessionKey": requestSessionKey,
        //     "userName": encryptedUserName
        // });

        // 5. 用户认证结束后，在调用结果查询 API 之前请生成新的会话。
        TossCertSession resultSession = tossCertSessionGenerator.generate();

        // 6. 请调用结果查询 API。
        // 请在结果查询 API 的 body 参数中添加认证请求中签发的 txId 和新生成的 sessionKey。
        String resultSessionKey = resultSession.getSessionKey();
        String txId = "a39c84d9-458d-47e4-acf7-c481e851f79b";
        // TossCertResultResponse response = getCertificationResult({
        //     "txId": txId,
        //     "sessionKey": resultSessionKey
        // });

        // 7. 为了进行解密，需要持有在结果查询请求中创建的 resultSession。
        // 假设 encryptedResultUserName 是从结果查询 API 收到的加密后的 userName。
        // decryptedUserName 已完成完整性验证。
        String encryptedResultUserName = "结果查询 API 返回的加密后的 userName";
        String decryptedUserName = resultSession.decrypt(encryptedResultUserName);
    }
}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
**请参考**

在 Toss 测试环境中，提供的不是实际用户的个人信息，而是 Toss 生成的虚拟人物的固定个人信息。
{% endhint %}

{% hint style="info" %}
**生成会话密钥时使用的 Public key**

```
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoVdxG0Qi9pip46Jw9ImSlPVD8+L2mM47ey6EZna7D7utgNdh8Tzkjrm1Yl4h6kPJrhdWvMIJGS51+6dh041IXcJEoUquNblUEqAUXBYwQM8PdfnS12SjlvZrP4q6whBE7IV1SEIBJP0gSK5/8Iu+uld2ctJiU4p8uswL2bCPGWdvVPltxAg6hfAG/ImRUKPRewQsFhkFvqIDCpO6aaeaR10q6wwENZltlJeeRnl02VWSneRmPqqypqCxz0Y+yWCYtsA+ngfZmwRMaFkXcWjaWnvSqqV33OAsrQkvuBHWoEEkvQ0P08+h9Fy2+FhY9TeuukQ2CVFz5YyOhp25QtWyQI+IaDKk+hLxJ1APR0c3tmV0ANEIjO6HhJIdu2KQKtgFppvqSrZp2OKtI8EZgVbWuho50xvlaPGzWoMi9HSCb+8ARamlOpesxHH3O0cTRUnft2Zk1FHQb2Pidb2z5onMEnzP2xpTqAIVQyb6nMac9tof5NFxwR/c4pmci+1n8GFJIFN18j2XGad1mNyio/R8LabqnzNwJC6VPnZJz5/pDUIk9yKNOY0KJe64SRiL0a4SNMohtyj6QlA/3SGxaEXb8UHpophv4G9wN1CgfyUamsRqp8zo5qDxBvlaIlfkqJvYPkltj7/23FHDjPi8q8UkSiAeu7IV5FTfB5KsiN8+sGSMCAwEAAQ==
```

{% endhint %}

默认建议使用 SDK，但也提供多种语言的代码示例。更详细的示例请 [这里](https://github.com/toss/toss-cert-examples)查看。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://developers-apps-in-toss.toss.im/documentation/api-and-sdk-zh/common/authentication/toss-auth.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
