# Callback API

## Callback 호출

<mark style="color:green;">`POST`</mark> `VEGA_VERIFICATION_CALLBACK_API_PATH`

{% hint style="info" %}
**Enclave 환경 변수:**

<mark style="color:green;">**`VEGA_VERIFICATION_CALLBACK_API_PATH`**</mark> 변수에, Callback API 엔드포인트를 명시합니다.
{% endhint %}

> 비동기 방식으로 API가 호출되었을 경우 사용되는 Callback API입니다.
>
> 비동기로 동작하는 API는 아래와 같습니다.
>
> * 사용자 검증 요청 API
> * 트랜잭션 결과 리포트 API
> * 에러 상황 리포트 API
> * Chainalysis KYT 기능을 이용한 위험 평가 API
> * Refinitiv WCO 기능을 이용한 위험 평가 API
>
> Callback API는 enclave에서 호출하는 API로 다음과 같은 경우에 호출됩니다.
>
> * 이전에 요청했던 Verification에 대한 결괏값이 도착했을 경우 (callbackType : VERIFICATION\_RESULT)
> * 송신 VASP에서 특정 verification에 대응되는 txHash 또는 txID의 report가 도착했을 경우 (callbackType : TX\_REPORT)
> * 송신 VASP에서 특정 verification에 대응되는 에러 상황 report가 도착했을 경우 (callbackType : ERROR\_REPORT)
> * 기존에 등록한 Chainalysis KYT 요청에 대한 위험 평가 결과를 수신했을 경우 (callbackType : CHAINALYSIS\_KYT\_RESULT)
> * 기존에 등록한 Refinitiv WCO 요청에 대한 위험 평가 결과를 수신했을 경우 (callbackType : REFINITIV\_WCO\_RESULT)

{% hint style="info" %}
**응답 시간:**

이 Callback API는 1초 이내에 응답해야 합니다.
{% endhint %}

{% hint style="info" %}
**구현 정책**

* **200 OK** 코드 외 다른 HTTP response status code를 반환해서는 안 됩니다.
* API에 대한 response는 가급적 빨리 반환해야 합니다. 따라서 callback API 내부적으로 시간이 오래 걸리는 작업은 비동기로 처리해야 합니다.
* 멱등성(Idempotent)을 보장하도록 구현해야 합니다.
  * enclave 내부에서 데이터 유실을 방지하기 위해 똑같은 데이터로 callback이 여러 번 호출될 수 있습니다.
  * 따라서 중복 데이터를 수신했을 경우에도 이전과 똑같은 동작을 하도록 보장해야 합니다. (예를 들어 이미 받은 데이터는 무시한다든지)
    {% endhint %}

## Request Header

| Field Name    | Description                                        |
| ------------- | -------------------------------------------------- |
| Authorization | Bearer \<VEGA\_VERIFICATION\_AUTHORIZATION\_TOKEN> |

## Request Body

<details>

<summary>Request Body Example for VERIFICATION_RESULT callbackType</summary>

```json
{
  "callbackType": "VERIFICATION_RESULT",
  "data": {
    "verificationUuid": "64ab871b-14a3-47df-9b80-368e29fe8180",
    "verificationResult": "DENIED",
    "reason": "UNKNOWN-ADDRESS",
    "ivms101": {
      "originator": {
        "originatorPersons": [
          {
            "naturalPerson": {
              "name": {
                "nameIdentifier": [
                  {
                    "primaryIdentifier": "James",
                    "secondaryIdentifier": "Din",
                    "nameIdentifierType": "LEGL"
                  }
                ]
              }
            }
          }
        ],
        "accountNumber": ["0x5811001506550d8356a215be229c15b6ef371a9a"]
      },
      "beneficiary": {
        "beneficiaryPersons": [
          {
            "naturalPerson": {
              "name": {
                "nameIdentifier": [
                  {
                    "primaryIdentifier": "Taylor",
                    "secondaryIdentifier": "Robbins",
                    "nameIdentifierType": "LEGL"
                  }
                ]
              }
            }
          }
        ],
        "accountNumber": ["0xb0bFf9721871e22653358956cf59a5FdBF3D752F"]
      },
      "originatingVASP": {
        "legalPerson": {
          "name": {
            "nameIdentifier": [
              {
                "legalPersonName": "Lambda256",
                "legalPersonNameIdentifierType": "LEGL"
              }
            ]
          },
          "geographicAddress": [
            {
              "addressType": "GEOG",
              "postcode": "123123c",
              "townName": "Yeoksam-dong",
              "addressLine": ["sechogu street", "100-100"],
              "country": "KR"
            }
          ],
          "nationalIdentification": {
            "nationalIdentifier": "KR0001",
            "nationalIdentifierType": "RAID",
            "registrationAuthority": "RA000151"
          }
        }
      },
      "beneficiaryVASP": {
        "legalPerson": {
          "name": {
            "nameIdentifier": [
              {
                "legalPersonName": "Lambda256 Exchange",
                "legalPersonNameIdentifierType": "LEGL"
              }
            ]
          },
          "geographicAddress": [
            {
              "addressType": "GEOG",
              "postcode": "234234",
              "townName": "Yeoksam-dong",
              "addressLine": ["sechogu street", "100-100"],
              "country": "KR"
            }
          ],
          "nationalIdentification": {
            "nationalIdentifier": "123456",
            "nationalIdentifierType": "RAID",
            "registrationAuthority": "RA000677"
          }
        }
      }
    }
  }
}
```

</details>

<details>

<summary>Request Body Example for TX_REPORT callbackType</summary>

```json
{
  "callbackType": "TX_REPORT",
  "data": {
    "verificationUuid": "64ab871b-14a3-47df-9b80-368e29fe8181",
    "txHash": "8a54d58ca4100112a5430818776d74898f2232770bae03046862575cb851a042",
    "vout": "2"
  }
}
```

</details>

<details>

<summary>Request Body Example for ERROR_REPORT callbackType</summary>

```json
{
  "callbackType": "ERROR_REPORT",
  "data": {
    "verificationUuid": "64ab871b-14a3-47df-9b80-368e29fe8182",
    "result": "ERROR",
    "reason": "BLACKLISTED",
    "message": "Originator is included in the blacklist."
  }
}
```

</details>

<details>

<summary>Request Body Example for CHAINALYSIS_KYT_RESULT callbackType</summary>

```json
{
  "callbackType": "CHAINALYSIS_KYT_RESULT",
  "data": {
    "verificationUuid": "69a310e6-810f-4a31-83d1-bcdafccf5304",
    "riskAssessment": {
      "chainalysisKYT": {
        "counterpartyVaspId": "15952089931162059995",
        "apiType": "ATTEMPT",
        "userId": "15952089931162059995",
        "direction": "OUTGOING",
        "network": "ETHEREUM",
        "asset": "ETH",
        "amount": "1",
        "usdPrice": "1820.17",
        "outputAddress": "bb3fd383d1c5540e52ef0a7bcb9433375793aeaf",
        "timestamp": "2023-05-18T12:39:44.000Z",
        "externalId": "79382ac9-c7be-3fab-ad56-8c61c654e2fc",
        "status": "PROCESSED",
        "alertCount": 1,
        "createdAt": "2023-05-18T12:39:46.000Z",
        "assessedAt": "2023-05-18T12:39:45.263Z"
      },
      "chainalysisKYTAlerts": [
        {
          "counterpartyVaspId": "15952089931162059995",
          "externalId": "79382ac9-c7be-3fab-ad56-8c61c654e2fc",
          "direction": "OUTGOING",
          "alertId": "118b8cc8-f579-11ed-b86d-a3210c6ca9b8",
          "alertLevel": "MEDIUM",
          "entityCategory": "high risk exchange",
          "serviceName": "HIGH RISK EXCHANGE: SimpleSwap.io bb3fd383d1c5540e52ef0a7bcb9433375793aeaf",
          "exposureType": "DIRECT",
          "alertAmount": "1820.17",
          "createdAt": "2023-05-18T12:39:52.461Z"
        }
      ]
    }
  }
}
```

</details>

<details>

<summary>Request Body Example for REFINITIV_WCO_RESULT callbackType</summary>

```json
{
  "callbackType": "REFINITIV_WCO_RESULT",
  "data": {
    "verificationUuid": "69a310e6-810f-4a31-83d1-bcdafccf5304",
    "riskAssessment": {
      "refinitivWorldCheckOne": {
        "counterpartyVaspId": "15952089931162058999",
        "direction": "INCOMING",
        "caseSystemId": "5jb7r2c9xjfk1hoc95gfayv6m",
        "status": "PROCESSED",
        "matchStrength": "EXACT",
        "aggregatedSummaryResult": "{\"caseId\":\"69a310e6-810f-4a31-83d1-bcdafccf5304-INCOMING-1684413585757\", ... }}}",
        "createdAt": "2023-05-18T12:39:48.000Z",
        "assessedAt": "2023-05-18T12:39:57.834Z"
      }
    }
  }
}
```

</details>

| Field Name   | Data Type | Required | Description                                      |
| ------------ | --------- | -------- | ------------------------------------------------ |
| callbackType | string    | true     | Callback API가 어떠한 상황에서 호출되었는지 구분하는 값. (하단 설명 참조) |
| data         | object    | true     | callbackType에 따라 data 형식이 달라집니다. (위 샘플 코드 참조)    |

* <mark style="color:blue;">**callbackType**</mark> 필드에는 다음과 같은 값이 들어갈 수 있습니다.
  * **VERIFICATION\_RESULT**: 이전에 호출했던 사용자 검증 요청 API에 대한 결과가 도착했을 때 호출
  * **TX\_REPORT**: Originating VASP로부터 트랜잭션 결과 리포트가 도착했을 때 호출
  * **ERROR\_REPORT**: Beneficiary VASP로부터 에러 상황 리포트가 도착했을 때 호출
  * **CHAINALYSIS\_KYT\_RESULT**: Chainalysis KYT API를 이용하여 위험 평가를 수행한 결과가 도출되었을 때 호출
  * **REFINITIV\_WCO\_RESULT**: Refinitiv WCO API를 이용하여 위험 평가를 수행한 결과가 도출되었을 때 호출
* <mark style="color:blue;">**data.reason**</mark> 필드에는 다음과 같은 값이 들어갈 수 있습니다.

  * 단, 사용자 검증 요청에 대한 결과 `data.result` 필드의 값이 **DENIED** 일 때에만 유효합니다.
  * `data.message` 필드에는, 아래 표에 설명된 값을 string으로 전달합니다. (하단 표 내용 참조)

  | <p>reason</p><p>(string)</p>      | <p>result</p><p>(string)</p> | <p>message</p><p>(string)</p>    | Description                                                       |
  | --------------------------------- | ---------------------------- | -------------------------------- | ----------------------------------------------------------------- |
  | `UNKNOWN-SYMBOL`                  | DENIED                       | 해당 VASP에서 취급하지 않는다고 판명된 심벌 이름    | 거래소에서 취급하지 않는 심벌일 경우 (VASP가 취급하지 않는 자산일 경우)                       |
  | `UNKNOWN-NETWORK`                 | DENIED                       | 해당 VASP에서 취급하지 않는다고 판명된 네트워크 이름  | 거래소에서 취급하지 않는 네트워크이거나 네트워크 정보가 불충분한 경우 (심벌은 동일하지만 네트워크가 맞지 않는 경우) |
  | `UNKNOWN-ADDRESS`                 | DENIED                       | 해당 VASP 소유의 주소가 아니라고 판명된 지갑 주소   | 가상 자산 주소가 해당 VASP의 주소가 아닌 경우                                      |
  | `LACK-OF-INFORMATION`             | DENIED                       | 부족한 개인 정보 필드 코드 목록 (쉼표로 구분)      | 상대방 사용자의 정보가 검증을 수행하기에 부족한 경우                                     |
  | `UNAVAILABLE-INFORMATION`         | DENIED                       | 넘겨줄 수 없는 개인 정보 필드 코드 목록 (쉼표로 구분) | 상대방이 요청한 개인 정보가 없거나 넘겨줄 수 없는 경우                                   |
  | `BLACKLISTED`                     | DENIED                       | -                                | 상대방 사용자에 대한 sanction screening 결과 문제가 있는 경우                       |
  | `UNVERIFIED-KYC`                  | DENIED                       | -                                | 자신의 VASP의 해당 사용자가 KYC 인증이 되어 있지 않은 경우                             |
  | `MISMATCHED-NAME`                 | DENIED                       | -                                | 수신자의 이름이 송신 VASP에서 보내준 이름과 일치하지 않는 경우                             |
  | `NOT-ALLOWED`                     | DENIED                       | 거부 사유                            | 수신 VASP에서 어떤 이유로든 해당 사용자 검증 요청을 거부한 경우                            |
  | `UNDEFINED-ERROR`                 | DENIED                       | 에러 내용                            | 그 밖에, 따로 정의되어 있지 않은 에러가 발생한 경우                                    |
  | `BENEFICIARY-ACCOUNT-NOT-MATCHED` | ERROR                        | -                                | 송신자가 입력한 수신자 주소가 수신 VASP에 의해 변경되어 반환되었을 경우                        |
  | `REQUEST-TIMEOUT`                 | ERROR                        | -                                | 수신 VASP에서 사용자 검증 VASP API 호출이 시간 초과로 실패했을 경우                      |

## Response Body

<details>

<summary>Response Body Examples</summary>

* 200 OK

```json
{
  "result": true
}
```

</details>

| Field Name | Data Type | Required | Example | Description           |
| ---------- | --------- | -------- | ------- | --------------------- |
| result     | boolean   | true     | true    | 결괏값. (에러는 반환하지 않습니다.) |


---

# Agent Instructions: 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:

```
GET https://docs-kr.verifyvasp.com/reference/vasp-api-reference/callback-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
