# Getting Started
To get started with IDnGO:
- Log in to the Dashboard.
- Set up user access for your company: invite your team and assign roles and permissions.
- Create a verification level.
- Customize the verification process for your service.
- Complete authentication.
- Choose the appropriate integration method:
WebSDK → iOS SDK → Android SDK → API integration →
# Authentication
To use the IDnGO SDK and API, you must complete authentication.
# App token
In the «Dev space» section of the Dashboard, generate an appToken and a secretKey. These values are required to obtain an accessToken.
Each environment (Sandbox or Production) requires a separate App token.
Warning:
appToken and secretKey are displayed only once at the time of creation. You will not be able to view or edit them later.
# Authorization headers
All API requests must include the following headers:
X-App-Token— the application token generated in the Dashboard;X-App-Access-Sig— the request signature (hexadecimal, lowercase);X-App-Access-Ts— the timestamp (seconds since Unix epoch in UTC).
Header names are case-insensitive and may vary depending on the implementation, both in your requests and in our responses.
# Request signature
The value of X-App-Access-Sig is generated using the HMAC-SHA256 algorithm with the secretKey (created together with the appToken).
The signature input is a string built by concatenating the following elements in order:
- Timestamp — the value of the
X-App-Access-Tsheader;
The timestamp must be within 1 minute of our server time. Make sure the time on your servers is correct.
HTTP method in uppercase (for example, GET or POST);
Request URI without the host name, starting with
/and including all query parameters, for example:/resources/applicants/123?fields=infoRequest body — if present, use the exact body as sent.
Example of the string for signing:
1607551635POST/resources/accessTokens?userId=cfd20712-24a2-4c7d-9ab0-146f3c142335&levelName=basic-kyc-level&ttlInSecs=600
Apply HMAC-SHA256 using your secretKey to this string, then encode the result in lowercase hexadecimal.
# POST Generate access token
When initializing the SDK, an accessToken is required. Each token is valid for a single applicant and cannot be reused for other applicants.
To obtain an accessToken, send the following request:
POST /resources/accessTokens?userId={userId}&levelName={levelName}
Make sure that the authorization headers of the request use the appToken and secretKey pair created in the correct environment (Sandbox or Production):
- For testing, use the
appTokenandsecretKeypair created in the Sandbox environment; - For real checks, use the
appTokenandsecretKeypair created in the Production environment.
Request parameters
| Name | Type | Required | Description |
|---|---|---|---|
userId | String | Yes | External identifier of the applicant linked to the token. Must match the externalUserId of the applicant. |
levelName | String | Yes | The name of the verification level, as configured in the «Application Levels» section of the Dashboard. |
ttlInSecs | Integer | No | Token validity period in seconds (default: 600). |
externalActionId | String | No | External identifier of the applicant action linked to the token. |
The userId request parameter should be unique and non-random. It can be the external identifier of the applicant in your system or an email address. Do not generate these identifiers randomly unless you are performing testing.
Warning:
If userId or levelName contains reserved characters (such as «@» or «+»), URL-encode the values to prevent signature mismatches.
Response
| Name | Type | Description |
|---|---|---|
token | String | The generated access token for the applicant verification. |
Example
curl -X POST \
'https://api.idngo.kz/resources/accessTokens?userId=JamesBond007&levelName=basic-kyc-level&ttlInSecs=600' \
-H 'Accept: application/json'
{
"token": "_act-b8ebfb63-5f24-4b89-9c08-5bbabeec986e",
"userId": "JamesBond007"
}
# Webhooks
IDnGO webhooks allow you to automatically receive notifications about events and changes related to the verification process of your users.
After an applicant verification is completed, we will send you a POST request with a JSON payload to the URL you provided during integration. Usually, the URLs differ for Sandbox and Production environments.
Warning:
- We do not send any information to an endpoint over HTTP, only HTTPS.
- Supported TLS protocol versions are 1.2 or higher.
- The number of webhooks is limited to 20.
- We do not transmit any personal data via webhooks. You can retrieve all recognized applicant data using this API method.
- If webhooks are not received, first check your endpoints using SSL Labs or Docker.
Make sure to test your webhook before sending its URL to us. The endpoint should not return an HTTP 500 response and must not require any authorization.
Webhook events can be monitored and analyzed in the «Webhook Logs» tab.
In the Sandbox environment, API integration does not perform automatic verification. You need to set verification parameters using this request. After that, verification results can be received via the applicantReviewed webhook.
If a webhook is not delivered for any reason, all delivery attempts are logged, and webhooks can be resent at any time. If a webhook fails to deliver, we retry four times: after 5 minutes, 1 hour, 5 hours, and 18 hours, until the request succeeds.
It is recommended to wait no longer than 24 hours for a webhook. If it has not arrived, send a request to the server to retrieve the applicant’s current status.
Check the createdAtMs field in the webhook payload to ensure you are receiving the most recent applicant status.
# Webhook types
You can configure webhook types, track their statuses, and resend them manually in the «Webhook manager» section.
| Value | Description |
|---|---|
applicantCreated | The applicant has been created. |
applicantPending | The user has uploaded all required documents, and the applicant is pending review. |
applicantReviewed | The applicant verification is complete. Contains the verification result. |
applicantOnHold | The applicant is awaiting a final decision from a compliance officer (manual review started). |
applicantReset | The applicant has been reset: the applicant’s status changed to init, and all documents marked inactive. New images/data are expected to be uploaded. |
applicantPersonalInfoChanged | The provided user information has been changed. |
applicantDeleted | The applicant has been permanently deleted. |
applicantLevelChanged | The verification level has been changed. |
applicantActionPending | The applicant actions are pending review. |
applicantActionReviewed | The applicant actions review has been completed. |
applicantActionOnHold | The applicant action is awaiting a final decision from a compliance officer. |
Webhook payload fields
| Name | Type | Required | Description |
|---|---|---|---|
applicantId | String | Yes | Unique identifier of the applicant in our system. |
inspectionId | String | Yes | Unique identifier of the inspection. |
correlationId | String | Yes | Unique identifier of the event on our side. |
levelName | String | No | The name of the verification level, as configured in the «Application Levels» section of the Dashboard. |
externalUserId | String | No | External applicant identifier — the unique identifier of the user in your system. |
type | String | Yes | Type of webhook event. |
sandboxMode | Boolean | Yes | Indicates whether the webhook was sent from the Sandbox environment (true/false). |
reviewStatus | String | Yes | Current applicant status. See the status reference for details. |
createdAtMs | Date | Yes | The date and time the webhook was created in UTC (YYYY-MM-dd hh:mm:ss.fff). |
applicantType | String | No | Applicant type, for example: individual/company. |
reviewResult | Object | No | Additional details about the verification result. |
applicantMemberOf | Array of objects | No | List of company profiles where the current applicant is a beneficiary. |
applicantActionId | String | No | Unique identifier of the applicant action. |
externalApplicantActionId | String | No | External unique identifier of the action in your system. |
clientId | String | Yes | Unique identifier of your company in our system. |
# Rejection reasons
The verification result webhook contains the rejectLabels field, which specifies one or more tags describing the reason for rejection.
These tags are intended only for analyzing check results and provide a general description of the issue. They should not be used to generate messages displayed to users.
| Rejection Tag ( rejectLabels value) | Rejection TypereviewRejectType | Description |
|---|---|---|
FORGERY | FINAL | Fraud attempt detected. |
SPAM | FINAL | Too many images were uploaded (photo spam). |
BAD_PROOF_OF_IDENTITY | RETRY | Identity document is invalid, unsuitable, missing, or of poor quality. |
SELFIE_MISMATCH | FINAL | The selfie does not match the photo in the submitted documents. |
ID_INVALID | RETRY | Identity document is invalid. |
DUPLICATE | FINAL | The applicant already has a verified profile; duplicates are not allowed. |
BAD_AVATAR | RETRY | Avatar does not meet requirements. |
WRONG_USER_REGION | FINAL | Applicants from this region/country are not accepted. |
INCOMPLETE_DOCUMENT | RETRY | Document is only partially visible; required data is missing. |
BLACKLIST | FINAL | The applicant has been blacklisted by IDnGO. |
BLOCKLIST | FINAL | The applicant has been blacklisted by the client. |
UNSATISFACTORY_PHOTOS | RETRY | Traces of editing in a graphic editor are visible on the image. |
DOCUMENT_PAGE_MISSING | RETRY | Required document pages are missing. |
DOCUMENT_DAMAGED | RETRY | The document is damaged. |
REGULATIONS_VIOLATIONS | FINAL | The applicant does not meet regulatory requirements. |
INCONSISTENT_PROFILE | FINAL | Data or documents of different people detected in one applicant. |
ADDITIONAL_DOCUMENT_REQUIRED | RETRY | Additional documents are required. |
AGE_REQUIREMENT_MISMATCH | FINAL | Applicant does not meet age requirements. |
EXPERIENCE_REQUIREMENT_MISMATCH | FINAL | Applicant does not meet experience requirements (for example, driving experience). |
CRIMINAL | FINAL | Applicant involved in illegal activities. |
WRONG_ADDRESS | RETRY | The address in the documents does not match the address provided by the user. |
GRAPHIC_EDITOR | RETRY | Document or photo edited in a graphic editor. |
DOCUMENT_DEPRIVED | RETRY | The document was confiscated. |
FRAUDULENT_PATTERNS | FINAL | Fraudulent behavior detected. |
NOT_ALL_CHECKS_COMPLETED | RETRY | Not all required checks have been completed. |
FRONT_SIDE_MISSING | RETRY | The front side of the document is missing. |
BACK_SIDE_MISSING | RETRY | The back side of the document is missing. |
SCREENSHOTS | RETRY | Screenshots uploaded instead of photos. |
BLACK_AND_WHITE | RETRY | Black-and-white document images uploaded. |
INCOMPATIBLE_LANGUAGE | RETRY | Translation of the document is required. |
EXPIRATION_DATE | RETRY | Document has expired. |
BAD_SELFIE | RETRY | Poor-quality selfie uploaded. |
BAD_FACE_MATCHING | RETRY | The face in the selfie cannot be matched with the document photo. |
BAD_PROOF_OF_ADDRESS | RETRY | Proof of address (PoA) document is of poor quality. |
FRAUDULENT_LIVENESS | FINAL | Attempt to bypass the liveness check detected. |
OTHER | RETRY | Other reason. |
PROBLEMATIC_APPLICANT_DATA | RETRY | The provided information does not match the information obtained from the document. |
OK | RETRY | Custom rejection tag. |
Example
# Example of a webhook payload for applicantReviewed:
{
"applicantId": "5cb744200a975a67ed1798a4", // applicant ID
"inspectionId": "5cb744200a975a67ed1798a5", // applicant's inspection ID
"correlationId": "req-fa94263f-0b23-42d7-9393-ab10b28ef42d",
"externalUserId": "externalUserId",
"type": "applicantReviewed",
"reviewResult": {
// A human-readable comment that can be shown to your end user
// Please note that individual images may also contain additional document-specific comments.
// In this case this field might be empty.
// In order to get them, refer to the Getting applicant status (API)
"moderationComment": "We could not verify your profile. Please contact support: support@idngo.kz",
// A human-readable comment that should not be shown to an end user, and is meant to be read by a client
// This field will contain applicant's top-level comments,
// plus, if the rejectType is not RETRY it may contain some private info, like that the user is a fraudster.
// we envision that this field will be used for admin areas of our clients,
// where a human can get all information
"clientComment": " Suspected fraudulent account.",
// final answer that should be highly trusted (only 'RED' and 'GREEN' are currently supported)
"reviewAnswer": "RED",
// a machine-readable constant that describes the problems in case of verification failure
"rejectLabels": ["UNSATISFACTORY_PHOTOS", "GRAPHIC_EDITOR", "FORGERY"],
"reviewRejectType": "FINAL"
},
// indicates that the verification process has been completed
// NOTE: it does not mean that the applicant was approved,
// it just means that an applicant was processed
"reviewStatus": "completed",
// time of webhook creation
"createdAtMs": "2020-02-21 13:23:19.129"
}
{
"applicantId": "5cb56e8e0a975a35f333cb83",
"inspectionId": "5cb56e8e0a975a35f333cb84",
"correlationId": "req-a260b669-4f14-4bb5-a4c5-ac0218acb9a4",
"externalUserId": "externalUserId",
"type": "applicantReviewed",
"reviewResult": {
"reviewAnswer": "GREEN"
},
"reviewStatus": "completed",
"createdAtMs": "2020-02-21 13:23:19.091"
}
# Example of a webhook payload for applicantCreated:
{
"applicantId": "5c9e177b0a975a6eeccf5960",
// inspection ID that contains a result
"inspectionId": "5c9e177b0a975a6eeccf5961",
// an ID to debug in case of unexpected errors (should be provided to IDnGO)
"correlationId": "req-63f92830-4d68-4eee-98d5-875d53a12258",
"levelName": "basic-kyc-level",
"externalUserId": "12672",
// type of webhook (see the corresponding section)
"type": "applicantCreated",
"sandboxMode": "false",
"reviewStatus": "init",
"createdAtMs": "2020-02-21 13:23:19.001",
"clientId": "idngoClient"
}
# Example of a webhook payload for applicantPending:
{
"applicantId": "5c7791f80a975a1df426b9e9",
"inspectionId": "5c7791f80a975a1df426b9ea",
"applicantType" : "individual",
"correlationId": "req-4af54c06-6a50-4cb9-a7dc-b94b2f5b07eb",
"levelName": "liveness-level",
"externalUserId": "12672",
"type": "applicantPending",
"sandboxMode": "false",
"reviewStatus": "pending",
"createdAtMs": "2020-02-21 13:23:19.001",
"clientId": "idngoClient"
}
# Example of a webhook payload for applicantOnHold:
{
"inspectionId": "5d10ca4e0a975a1c4cc30bbb",
"applicantType" : "individual",
"correlationId": "req-a98abc30-a5d9-4e1d-bab4-2f1af64bd5a5",
"levelName": "poa-level",
"externalUserId": "12672",
"reviewStatus": "onHold",
"applicantId": "5d10ca4e0a975a1c4cc30bba",
"type": "applicantOnHold",
"sandboxMode": "true",
"createdAtMs": "2020-02-21 13:23:19.001",
"clientId": "idngoClient"
}
# Example of a webhook payload for applicantPersonalInfoChanged:
{
"applicantId" : "5ede51230a975a19a19ba5c1",
"inspectionId" : "5ede51230a975a19a19ba5c2",
"applicantType" : "individual",
"correlationId" : "req-60103dee-79f1-43f4-bdcc-eb2554556afa",
"levelName": "id+liveness",
"externalUserId" : "12672",
"type" : "applicantPersonalInfoChanged",
"sandboxMode": "false",
"reviewResult" : {
"reviewAnswer" : "GREEN"
},
"reviewStatus" : "completed",
"createdAtMs" : "2020-06-08 19:39:29.001",
"clientId": "idngoClient"
}
# Example of a webhook payload for applicantDeleted:
{
"applicantId": "5f194e74040c3f316bda271c",
"inspectionId": "5f194e74040c3f316bda271d",
"applicantType": "individual",
"correlationId": "req-d34c974c-5935-41b8-a0a9-cedd2407eada",
"levelName": "phone-level",
"externalUserId": "12672",
"type": "applicantDeleted",
"sandboxMode": "false",
"reviewStatus": "init",
"createdAtMs": "2020-07-23 11:18:33.001",
"clientId": "idngoClient"
}
# Example of a webhook payload for applicantLevelChanged:
{
"applicantId": "5f194e74040c3f316bda271c",
"inspectionId": "5f194e74040c3f316bda271d",
"applicantType": "individual",
"correlationId": "req-d34c974c-5935-41b8-a0a9-cedd2407eadd",
"levelName": "basic-kyc-level",
"externalUserId": "12672",
"type": "applicantLevelChanged",
"sandboxMode": "false",
"reviewStatus": "init",
"createdAtMs": "2020-07-23 11:19:33.002",
"clientId": "idngoClient"
}
# Example of a webhook payload for applicantReset:
{
"applicantId": "5f194e74040c3f316bda271c",
"inspectionId": "5f194e74040c3f316bda271d",
"applicantType": "individual",
"correlationId": "req-57fed49a-07b8-4413-bdaa-a1be903769e9",
"levelName": "basic-kyc-level",
"externalUserId": "12672",
"type": "applicantReset",
"sandboxMode": "false",
"reviewResult": {
"reviewAnswer": "GREEN"
},
"reviewStatus": "init",
"createdAtMs": "2021-03-01 11:34:51.001",
"clientId": "idngoClient"
}
# Webhook sender verification
It is not recommended to rely on our IP addresses when creating a whitelist of webhook senders, as they may change from time to time.
To ensure that a webhook is sent by us, you can use HMAC-based signing. To enable this feature, set a secret key for each webhook and select an HMAC algorithm in the «Dev space».
When signing is enabled, we add an additional HTTP header X-Payload-Digest-Alg, which specifies the algorithm used:
HMAC_SHA1_HEX— deprecatedHMAC_SHA256_HEX— used by default when creating a new webhookHMAC_SHA512_HEX
Webhook sender verification instructions:
- Retrieve the webhook header value
x-payload-digestand the payload exactly as it is, without any modifications or conversion to JSON. - Get the HTTP webhook body in bytes.
- Compute the digest using the original payload in bytes and the HMAC algorithm specified in the
x-payload-digest-algheader. - Compare the
x-payload-digestheader value with the digest you calculated.
Webhook sender verification: POST /resources/inspectionCallbacks/testDigest?secretKey={secretKey}
# Example request to your endpoint:
curl -X POST \
'https://callbackurl.com/kyc' \
-H 'Content-Type: application/json' \
-d '{
"applicantId": "5cb56e8e0a975a35f333cb83",
"inspectionId": "5cb56e8e0a975a35f333cb84",
"correlationId": "req-ec508a2a-fa33-4dd2-b93d-fcade2967e03",
"externalUserId": "12672",
"type": "applicantReviewed",
"reviewResult": {
"reviewAnswer": "GREEN"
},
"reviewStatus": "completed",
"createdAtMs": "2020-02-21 13:23:19.111",
"clientId": "idngoClient"
}'
# Example of calculating the digest value:
export function checkDigest(req): boolean {
const algo = {
'HMAC_SHA1_HEX': 'sha1',
'HMAC_SHA256_HEX': 'sha256',
'HMAC_SHA512_HEX': 'sha512',
}[req.headers['X-Payload-Digest-Alg']]
if (!algo) {
throw new Error('Unsupported algorithm')
}
const calculatedDigest = crypto
.createHmac(algo, CYBERIRTY_PRIVATE_KEY)
.update(req.rawBody)
.digest('hex')
return calculatedDigest === req.headers['x-payload-digest']
}
private async Task<bool> CheckDigest(HttpRequest request)
{
using (var reader = new StreamReader(request.Body))
{
var body = await reader.ReadToEndAsync();
byte[] byteArray = Encoding.UTF8.GetBytes(body);
MemoryStream stream = new MemoryStream(byteArray);
string algo = Request.Headers["x-payload-digest-alg"];
var calculateDigest = new { };
switch (algo)
{
case "HMAC_SHA1_HEX":
HMACSHA1 hmacsha1 = new HMACSHA1(Encoding.UTF8.GetBytes(_verificationAccessor.idngoPrivateKey));
calculateDigest = hmacsha1.ComputeHash(stream).Aggregate("", (s, e) => s + String.Format("{0:x2}", e), s => s);
break;
case "HMAC_SHA256_HEX":
HMACSHA256 hmacsha256 = new HMACSHA256(Encoding.UTF8.GetBytes(_verificationAccessor.idngoPrivateKey));
calculateDigest = hmacsha1.ComputeHash(stream).Aggregate("", (s, e) => s + String.Format("{0:x2}", e), s => s);
break;
case "HMAC_SHA512_HEX":
HMACSHA512 hmacsha512 = new HMACSHA512(Encoding.UTF8.GetBytes(_verificationAccessor.idngoPrivateKey));
calculateDigest = hmacsha512.ComputeHash(stream).Aggregate("", (s, e) => s + String.Format("{0:x2}", e), s => s);
break;
default:
HMACSHA256 hmacsha256 = new HMACSHA256(Encoding.UTF8.GetBytes(_verificationAccessor.idngoPrivateKey));
calculateDigest = hmacsha1.ComputeHash(stream).Aggregate("", (s, e) => s + String.Format("{0:x2}", e), s => s);
break;
}
return calculateDigest == Request.Headers["x-payload-digest"];
};
}
public function validateWebHook(HttpRequest $request, string $content): void
{
$algo = match($request->headers->get('X-Payload-Digest-Alg')) {
'HMAC_SHA1_HEX' => 'sha1',
'HMAC_SHA256_HEX' => 'sha256',
'HMAC_SHA512_HEX' => 'sha512',
default => throw new \RuntimeException('Unsupported algorithm'),
};
$res = $request->headers->get('X-Signature') === hash_hmac(
$algo,
$content,
'your_secret_key'
);
if (!$res) {
$this->logger->error('Webhook Idngo sign ' . $content);
throw new LogicProfileException('Webhook Idngo sign ' . $content);
}
}