---
title: "Resubmit KYC after a rejection"
description: "Read why KYC failed, then send the person back to the hosted page or replace the rejected document, without creating a new customer."
---
This recipe reads why KYC failed, then sends the person back to the hosted page or replaces the rejected document, without creating a new customer.

**Who it is for.** This recipe is for the remittance app from [Verify a person with documents you upload](/recipes/customers-and-kyc/verify-a-person-with-documents-you-upload), after Kofi's verification came back `failed` because his selfie was blurred.

**Status.** Available. You cannot test it in sandbox today, because sandbox never records a rejection.

## Money example

1. Kofi is rejected, so he stays on the lowest limits, with a cap of 500.00 USD a day.
2. His 800.00 USD rent payout would pause on the daily limit.
3. He resubmits a clear selfie and is approved. His limit becomes 50,000.00 USD a day, and the 800.00 USD goes through.

## Before you start

- Kofi's `basic` row is `failed`. To check, read `kyc[type=basic].status` on [`GET /v1/customers/{customer_id}`](/api-reference/customers/get-v1-customers-customer-id).
- Know whether he was verified the first time on the hosted page or with documents you uploaded.

## Find out what is required

1. **The reason.** Read `failure_reason` in the `customer.kyc.status.failed` webhook, or `remark` on the `basic` row. It holds the reviewer's comment, the reasons as sentences, or only `Verification failed`.
2. **The detail.** Read `rejectLabels` and `reviewRejectType` in the review status that `GET /v1/customers/{customer_id}/kyc/status` returns.

<CodeGroup>
```bash title="cURL"
curl --request GET \
     --url https://api.gravv.xyz/v1/customers/9d2f4b6a-1c3e-4a5b-8d7f-2e4a6c8e0b13/kyc/status \
     --header 'Api-Key: <API_KEY>'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/customers/9d2f4b6a-1c3e-4a5b-8d7f-2e4a6c8e0b13/kyc/status", {
  method: "GET",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
  },
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.get(
    "https://api.gravv.xyz/v1/customers/9d2f4b6a-1c3e-4a5b-8d7f-2e4a6c8e0b13/kyc/status",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "reviewStatus": "completed",
    "reviewResult": {
      "reviewAnswer": "rejected",
      "rejectLabels": ["BAD_SELFIE"],
      "reviewRejectType": "RETRY"
    },
    "reviewDate": "2026-10-13 11:20:40+0000",
    "priority": 0
  },
  "error": null
}
```

- `RETRY` means the person may resubmit. Retake only what the labels name.
- `FINAL` means the reviewer does not expect a resubmission to pass. Contact support rather than trying again.

## Steps

### If you used the hosted page

Start again with a **new** key. From `failed`, the start returns a new link, valid for 1 hour, and sets the row back to `pending`, with **no new fee**.

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v1/customers/kyc/start \
     --header 'Api-Key: <API_KEY>' \
     --header 'Idempotency-Key: 2c8a4d99-2860-4d9c-bd69-a2abcc0ce82e' \
     --header 'Content-Type: application/json' \
     --data '
{
  "customer_id": "9d2f4b6a-1c3e-4a5b-8d7f-2e4a6c8e0b13"
}
'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/customers/kyc/start", {
  method: "POST",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
    "Idempotency-Key": "2c8a4d99-2860-4d9c-bd69-a2abcc0ce82e",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "customer_id": "9d2f4b6a-1c3e-4a5b-8d7f-2e4a6c8e0b13"
  }),
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.post(
    "https://api.gravv.xyz/v1/customers/kyc/start",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
        "Idempotency-Key": "2c8a4d99-2860-4d9c-bd69-a2abcc0ce82e",
        "Content-Type": "application/json",
    },
    json={
        "customer_id": "9d2f4b6a-1c3e-4a5b-8d7f-2e4a6c8e0b13",
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "status": "success",
    "review_status": "pending",
    "web_url": "https://<verification-host>/websdk/p/R7u2LwQ8sd"
  },
  "error": null
}
```

Send Kofi the link. He retakes only what was rejected.

### If you uploaded the documents

1. Upload the rejected document again with the **same `idDocType` and `idDocSubType`**, here `SELFIE`. The new file replaces the old one.

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v1/customers/kyc/upload-document \
     --header 'Api-Key: <API_KEY>' \
     --header 'Idempotency-Key: c5df1c69-b7f1-4abf-a416-2667956a483d' \
     --header 'Content-Type: application/json' \
     --data '
{
  "customer_id": "9d2f4b6a-1c3e-4a5b-8d7f-2e4a6c8e0b13",
  "filename": "selfie-retake.jpg",
  "content": "/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYI",
  "metadata": {
    "idDocType": "SELFIE"
  }
}
'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/customers/kyc/upload-document", {
  method: "POST",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
    "Idempotency-Key": "c5df1c69-b7f1-4abf-a416-2667956a483d",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "customer_id": "9d2f4b6a-1c3e-4a5b-8d7f-2e4a6c8e0b13",
    "filename": "selfie-retake.jpg",
    "content": "/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYI",
    "metadata": {
      "idDocType": "SELFIE"
    }
  }),
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.post(
    "https://api.gravv.xyz/v1/customers/kyc/upload-document",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
        "Idempotency-Key": "c5df1c69-b7f1-4abf-a416-2667956a483d",
        "Content-Type": "application/json",
    },
    json={
        "customer_id": "9d2f4b6a-1c3e-4a5b-8d7f-2e4a6c8e0b13",
        "filename": "selfie-retake.jpg",
        "content": "/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYI",
        "metadata": {
            "idDocType": "SELFIE",
        },
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

2. Start verification again with a new key. **Gravv bills this start again**, so first confirm that the upload replaced the right document.

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v1/customers/kyc/start-s2s \
     --header 'Api-Key: <API_KEY>' \
     --header 'Idempotency-Key: a47a1d26-8526-4ea3-890b-d8e7b747c770' \
     --header 'Content-Type: application/json' \
     --data '
{
  "customer_id": "9d2f4b6a-1c3e-4a5b-8d7f-2e4a6c8e0b13"
}
'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/customers/kyc/start-s2s", {
  method: "POST",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
    "Idempotency-Key": "a47a1d26-8526-4ea3-890b-d8e7b747c770",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "customer_id": "9d2f4b6a-1c3e-4a5b-8d7f-2e4a6c8e0b13"
  }),
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.post(
    "https://api.gravv.xyz/v1/customers/kyc/start-s2s",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
        "Idempotency-Key": "a47a1d26-8526-4ea3-890b-d8e7b747c770",
        "Content-Type": "application/json",
    },
    json={
        "customer_id": "9d2f4b6a-1c3e-4a5b-8d7f-2e4a6c8e0b13",
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

### Then wait

Wait for `customer.kyc.status.completed` or `customer.kyc.status.failed`, as you did the first time.

### If the customer record was wrong

If a name, date of birth or address on the customer was wrong, fix it first with [`PUT /v1/customers/{customer_id}`](/api-reference/customers/put-v1-customers-customer-id). Only the fields you send change, and you cannot blank a field. Editing the record does not reset KYC.

## What can go wrong

| Response | Why | Fix |
| :-- | :-- | :-- |
| The start returns the old, expired link | You reused the first key, so Gravv replayed the stored response | Use a new key |
| 409 `KYC_ALREADY_COMPLETED: …` on start-s2s | He was approved already, for example by an earlier retry | You don't need to do anything |
| The same rejection again | You sent the same file again, or replaced a different side or type | Read `rejectLabels`, then replace that exact type and sub-type |
| `failed` again with `reviewRejectType` `FINAL` | The reviewer made a final decision | Contact support. Do not keep resubmitting, because Gravv bills each server-to-server start |
| PUT 400 `only company emails are allowed` when changing an individual's email | The company-email rule applies to individuals on update too, a known issue | Change only the fields that were wrong. Contact support if the customer needs a free-mail address |
| PUT 400 `failed to update customer: email already exists` | The email belongs to another customer | Use another email |

## Webhooks you will receive

You receive `customer.kyc.status.pending` on the restart, then `customer.kyc.status.completed` or `customer.kyc.status.failed` with `failure_reason`. See [Customer and KYC events](/platform/webhooks/event-catalogue#customers-and-kyc).

## Testing in sandbox

You cannot test this recipe end to end in sandbox. Sandbox reviews reject test images, but the row stays `pending` and Gravv sends no `failed` webhook. Sandbox never produces a `failed` row. You can test the calls. A start from `pending` with a new key returns a new link.

## Reference

- [Get applicant verification status](/api-reference/kyc/get-v1-customers-customer-id-kyc-status)
- [Start KYC verification](/api-reference/kyc/post-v1-customers-kyc-start)
- [Upload a KYC document](/api-reference/kyc/post-v1-customers-kyc-upload-document)
- [Initiate server-to-server verification](/api-reference/kyc/post-v1-customers-kyc-start-s2s)
- [Update a customer](/api-reference/customers/put-v1-customers-customer-id)
