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.

FieldRequired forDescription
idDocTypeAll documentsDocument type. Individual customers: PASSPORT, ID_CARD, DRIVERS, or SELFIE. Business customers: REGISTRATION_CERTIFICATE.
countryAll documentsIssuing country as a 3-letter uppercase ISO code, such as NGA.
idDocSubTypeIdentity documentsDocument side: FRONT_SIDE or BACK_SIDE. Omit for SELFIE.
numberIdentity documents and registration certificatesDocument identification number.
issuedDateIdentity documentsIssue date in YYYY-MM-DD format.
validUntilIdentity documentsExpiration date in YYYY-MM-DD format.
firstNameIdentity documentsFirst name as shown on the document.
lastNameIdentity documentsLast name as shown on the document.
middleNameOptionalMiddle name as shown on the document.
dobOptionalDate of birth in YYYY-MM-DD format.
placeOfBirthOptionalPlace 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
}


Did this page help you?