> For the complete documentation index, see [llms.txt](https://docs.musepay.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.musepay.io/integration/webhook/kyc.md).

# KYC

Final results for standalone merchant KYC arrive as HTTP `POST` on your shared merchant callback URL. Enable it during onboarding; it is not a per-request `notify_url`, so do not repoint a URL that other products depend on. This product sends merchant callbacks only — no email, push, or in-app notice to the end user.

## When it is sent

* Approval: `kycStatus` is `APPROVED`.
* Final rejection: `kycStatus` is `REJECTED` with `reviewRejectType` `FINAL`.
* Requests for additional information and retryable rejections (`RETRY`) are not final results and send nothing.

Both outcomes settle the verification fee. Delivery is asynchronous and may be delayed or repeated, so a missing callback tells you nothing about the order or the fee. A received callback means the fee order is settled; confirm amounts with [Query Order](/reference/api-reference/acquiring-api/query-order.md) using `feeOrderNo`.

## Security

{% hint style="warning" %}
**`KYC_RESULT` has no RSA signature and no `sign` field.** The verification procedure in the [Webhook overview](/integration/webhook.md) does not apply here. API requests you send are still signed as described in [Authentication](/integration/authentication.md).
{% endhint %}

Serve over HTTPS, and do not treat HTTPS as proof of origin. Never unlock business operations on an unsigned callback alone: check the request-to-user association you stored, then confirm with [Query KYC Status](/reference/api-reference/kyc-api/query.md). Contact MusePay if the two disagree.

## Payload

Callback fields use camelCase, unlike the snake\_case fields in API responses.

| Name | Type   | Description                 |
| ---- | ------ | --------------------------- |
| type | String | Always `KYC_RESULT`.        |
| data | Object | Verification result, below. |

### `data`

| Name              | Type            | Description                                                                                              |
| ----------------- | --------------- | -------------------------------------------------------------------------------------------------------- |
| feeOrderNo        | String          | Settled fee order, matching `fee_order_no` from the link response.                                       |
| userId            | Number          | MusePay user ID. The API returns `user_id` as a string, so normalise before comparing.                   |
| requestId         | String          | The `request_id` of the original link request.                                                           |
| levelName         | String          | `L1`, `L2`, `L1_AML`, or `L2_AML`.                                                                       |
| kycStatus         | String          | `APPROVED` or `REJECTED`.                                                                                |
| kycLevel          | Number          | Highest level obtained through this product: `0`/`1`/`2`. On rejection, the level held before it.        |
| userXid           | String          | Optional merchant-side identifier; it may be omitted, so associate the user by `requestId` and `userId`. |
| moderationComment | String          | Optional review comment.                                                                                 |
| rejectLabels      | Array of String | Optional rejection labels.                                                                               |
| reviewRejectType  | String          | Optional; `FINAL` for a final rejection.                                                                 |

### Approval example

```json
{
  "type": "KYC_RESULT",
  "data": {
    "feeOrderNo": "2026101500000000000000000001",
    "userId": 1000001,
    "requestId": "kyc-link-202610150001",
    "levelName": "L1",
    "kycStatus": "APPROVED",
    "kycLevel": 1
  }
}
```

### Final rejection example

```json
{
  "type": "KYC_RESULT",
  "data": {
    "feeOrderNo": "2026101500000000000000000001",
    "userId": 1000001,
    "requestId": "kyc-link-202610150002",
    "levelName": "L1_AML",
    "kycStatus": "REJECTED",
    "kycLevel": 0,
    "moderationComment": "Example final rejection",
    "rejectLabels": ["EXAMPLE_LABEL"],
    "reviewRejectType": "FINAL"
  }
}
```

The comment and labels are examples, not an enumeration.

## Acknowledging

* Deduplicate on `type`, `data.requestId`, and `data.userId`. Signature-based or user-only deduplication would drop later applications.
* Return HTTP `200` once the message is durably stored; run slow checks in your own worker.
* Retries follow the [Webhook retry policy](/integration/webhook.md#retry-attempts). Handle duplicates and delays, and query or contact MusePay when a result is missing.
