KYC (new)

Know Your Customer (KYC) verification confirms a customer's identity before you create an account or activate certain features. Gravv requires KYC to comply with financial regulations and prevent fraud.

You can submit documents for verification using two methods: a dedicated interface or the API. Whichever method you use, check the outcome with the Get applicant verification status endpoint.

Required documents

Individual customers must provide a selfie and one government-issued identity document:

  • Passport
  • National ID card
  • Driver's license

Business customers must provide the business's registration certificate. Before you start verification for a business, complete KYC verification for every associated person linked to it.

Submit through a dedicated interface

Use the Start KYC verification endpoint to have a customer upload their identity documents (IDs) through a dedicated interface. The endpoint requires only the customer's ID:

curl --request POST \
     --url https://api.gravv.xyz/v1/customers/kyc/start \
     --header 'Api-Key: <API_KEY>' \
     --header 'Idempotency-Key: order_789_attempt_1' \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "customer_id": "c239895a-0436-4287-a37a-c2664f68d187"
}
'

You'll receive a response that includes the verification status and a web_url link. Share this link with the customer so they can upload their ID documents:

{
  "data": {
    "review_status": "pending",
    "status": "success",
    "web_url": "https://in.kyc.com/websdk/p/Cz3WKRuxvlmuS2kp"
  },
  "error": null
}

Submit through the API

To submit documents through the API, Base64-encode each document, upload it with the Upload document endpoint, then call the Initiate server-to-server verification endpoint.

Upload documents

Upload each required document with the Upload document endpoint. Encode the document in Base64 and send it as the content value. The Idempotency-Key header is required.

curl --request POST \
     --url https://api.gravv.xyz/v1/customers/kyc/upload-document \
     --header 'Api-Key: <API_KEY>' \
     --header 'Idempotency-Key: passport_upload_001' \
     --header 'content-type: application/json' \
     --data '
{
  "customer_id": "aabe0a33-6716-42e2-bbca-7abf1a8bd91c",
  "metadata": {
    "idDocType": "PASSPORT",
    "idDocSubType": "FRONT_SIDE",
    "country": "NGA",
    "firstName": "John",
    "lastName": "Doe",
    "number": "A12345678",
    "issuedDate": "2020-01-15",
    "validUntil": "2030-01-15"
  },
  "content": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAY...",
  "filename": "passport_front.jpg"
}
'

A successful upload returns a confirmation:

{
  "data": {
    "status": "success",
    "customer_id": "aabe0a33-6716-42e2-bbca-7abf1a8bd91c",
    "document_type": "PASSPORT",
    "country": "NGA",
    "message": "Document saved. Use /start-s2s to submit for verification."
  },
  "error": null
}

The metadata object describes the document. All idDocType and idDocSubType values are uppercase, and country is a 3-letter uppercase ISO code.

Field Required for Description
idDocType All documents Document type. Individual customers: PASSPORT, ID_CARD, DRIVERS, or SELFIE. Business customers: REGISTRATION_CERTIFICATE.
country All documents Issuing country as a 3-letter uppercase ISO code, such as NGA.
idDocSubType Identity documents Document side: FRONT_SIDE or BACK_SIDE. Omit for SELFIE.
number Identity documents and registration certificates Document identification number.
issuedDate Identity documents Issue date in YYYY-MM-DD format.
validUntil Identity documents Expiration date in YYYY-MM-DD format.
firstName Identity documents First name as shown on the document.
lastName Identity documents Last name as shown on the document.
middleName Optional Middle name as shown on the document.
dob Optional Date of birth in YYYY-MM-DD format.
placeOfBirth Optional Place of birth as shown on the document.

Keep these requirements in mind:

  • The maximum document size is 10 MB.
  • The filename extension and the decoded content type must agree, and must be .jpg, .jpeg, .png, or .pdf.
  • A selfie must be an image (JPEG or PNG); PDF selfies are rejected.
  • Omit the idDocSubType field for selfie uploads.

A business customer uploads a registration certificate, which may be a formation document or an ownership document. The idDocType value is always REGISTRATION_CERTIFICATE:

curl --request POST \
     --url https://api.gravv.xyz/v1/customers/kyc/upload-document \
     --header 'Api-Key: <API_KEY>' \
     --header 'Idempotency-Key: reg_cert_upload_001' \
     --header 'content-type: application/json' \
     --data '
{
  "customer_id": "c84d9a52-2978-43e5-9b86-eac9839e1147",
  "metadata": {
    "idDocType": "REGISTRATION_CERTIFICATE",
    "country": "NGA",
    "number": "RC123456"
  },
  "content": "JVBERi0xLjcKJ...",
  "filename": "registration_certificate.pdf"
}
'

Initiate verification

After uploading all required documents, call the Initiate server-to-server verification endpoint using the customer's id:

curl --request POST \
     --url https://api.gravv.xyz/v1/customers/kyc/start-s2s \
     --header 'Api-Key: <API_KEY>' \
     --header 'Idempotency-Key: kyc_initiate_001' \
     --header 'content-type: application/json' \
     --data '
{
  "customer_id": "aabe0a33-6716-42e2-bbca-7abf1a8bd91c"
}
'

Gravv starts the review and delivers the outcome through a webhook.

The API response for a pending verification is as follows:

{
  "data": {
    "status": "pending",
    "message": "KYC verification initiated. Status will be updated via webhook."
  },
  "error": null
}

For a business customer, the request fails if no registration certificate has been uploaded:

{
  "data": null,
  "error": {
    "code": "INVALID_REQUEST",
    "message": "business customers must upload a REGISTRATION_CERTIFICATE document before starting KYC verification"
  }
}

Check verification status

Whether the customer completed verification through the dedicated interface or the API, check the outcome with the Get applicant verification status endpoint. Gravv also notifies you through a webhook when the review finishes.

While the review is in progress, the value of the reviewStatus field is pending and there's no reviewResult field:

{
  "data": {
    "reviewStatus": "pending",
    "priority": 0
  },
  "error": null
}

Once the review finishes, reviewStatus is completed and reviewResult holds the decision:

{
  "data": {
    "reviewStatus": "completed",
    "reviewResult": {
      "reviewAnswer": "approved"
    },
    "reviewDate": "2026-06-19 21:59:00+0000",
    "priority": 0
  },
  "error": null
}

A rejected review lists the reason codes in rejectLabels, and reviewRejectType indicates whether the customer can resubmit:

{
  "data": {
    "reviewStatus": "completed",
    "reviewResult": {
      "reviewAnswer": "rejected",
      "rejectLabels": [
        "BAD_SELFIE",
        "SCREENSHOTS",
        "UNSATISFACTORY_PHOTOS"
      ],
      "reviewRejectType": "RETRY"
    },
    "reviewDate": "2026-06-19 20:44:19+0000",
    "priority": 0
  },
  "error": null
}

The status data object contains:

  • reviewStatus: pending while the review is in progress, or completed once a decision is returned.
  • reviewResult: the decision, present once the review is completed.
    • reviewAnswer: approved or rejected.
    • rejectLabels: reason codes for a rejection, present only when reviewAnswer is rejected.
    • reviewRejectType: the rejection category, such as RETRY, present only when reviewAnswer is rejected.
  • reviewDate: the decision timestamp, present once the review is completed.
  • priority: the applicant's review priority.

Get uploaded documents

To see which documents a customer has uploaded, call the Get KYC document data endpoint. It returns the metadata for each uploaded document:

{
  "data": {
    "customer_id": "aabe0a33-6716-42e2-bbca-7abf1a8bd91c",
    "documents": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "IDType": "PASSPORT",
        "IDSubType": "FRONT_SIDE",
        "IDNumber": "A12345678",
        "IssuingCountry": "NGA",
        "IssuedDate": "2020-01-01",
        "ExpiryDate": "2030-01-01",
        "has_front_image": true,
        "has_back_image": false,
        "OriginalFilename": "passport_front.jpg"
      }
    ],
    "count": 1
  },
  "error": null
}
Was this page helpful?