Identity Gram External API

To send data from your application use the following code to encrypt data before sending to the external api which you can checkout on swagger. Following vaiables must be present in encryptedData.

  1. first_name
  2. last_name
  3. unique_identifier


    const data = JSON.stringify({"first_name": "John", "last_name": "Doe", "email": "a@a.com", "unique_identifier": "xxxxxxx-xxxxxxxxx-xxxx"})
    function encrptData(data: string){
        const algorithm = "aes-256-gcm";
        const publicKey = "2eaf548c6022de5a3293fbfdd595afad04f750d9cc861d9c723229bb34fa679f".substring(0, 24);
        const privateKey = Buffer.from("2eaf548c6022de5a3293fbfdd595afad04f750d9cc861d9c723229bb34fa679f", 'hex');
        const cipher = crypto.createCipheriv(algorithm, privateKey, publicKey);
        let enc = cipher.update(data, 'utf8', 'hex');
        enc += cipher.final('hex');
        return enc;
    }

Java Implementation for encryption


    import javax.crypto.Cipher;
    import javax.crypto.SecretKey;
    import javax.crypto.spec.GCMParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    import java.nio.charset.StandardCharsets;
    import java.util.Base64;

    public class Main {
        public static void main(String[] args) throws Exception {
            String encryptedData = encryptData("test", "testing", "admin@user.com", "1");
            System.out.println(encryptedData);
        }

        public static String encryptData(String firstName, String lastName, String email, String Id) throws Exception {
            String data = "{\"first_name\":\""+firstName+"\",\"last_name\":\""+lastName+"\",\"email\":\""+email+"\",\"unique_identifier\":\""+Id+"\"}";
            String algorithm = "AES/GCM/NoPadding";
            String publicKey = "2eaf548c6022de5a3293fbfdd595afad04f750d9cc861d9c723229bb34fa679f".substring(0, 24);
            String privateKeyHex = "2eaf548c6022de5a3293fbfdd595afad04f750d9cc861d9c723229bb34fa679f";

            byte[] publicKeyBytes = publicKey.getBytes(StandardCharsets.UTF_8);
            byte[] privateKeyBytes = hexStringToByteArray(privateKeyHex);

            SecretKey secretKey = new SecretKeySpec(privateKeyBytes, "AES");
            Cipher cipher = Cipher.getInstance(algorithm);

            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, publicKeyBytes);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmParameterSpec);

            byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));

            return bytesToHex(encryptedBytes).substring(0, 184);
        }

        public static byte[] hexStringToByteArray(String hexString) {
            int length = hexString.length();
            byte[] byteArray = new byte[length / 2];
            for (int i = 0; i < length; i += 2) {
                byteArray[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) +
                        Character.digit(hexString.charAt(i + 1), 16));
            }
            return byteArray;
        }

        public static String bytesToHex(byte[] bytes) {
            StringBuilder result = new StringBuilder();
            for (byte aByte : bytes) {
                result.append(String.format("%02x", aByte));
            }
            return result.toString();
        }
    }

*Public and Private keys can be retrived from the admin panel inside integeration

Callback URL in Admin Panel

For callback url you can following parameters in the admin panel.

  1. user_id: will containg the unique_identifier sent when requesting in api creating verification.
  2. status: will contain the current status of the verification
  3. email: will contain email if it was provided at the time of creation

Callback URL should look something like the following

    
    http://example.com/verification-completed?user_id=[user_id]&status=[status]&email=[email]

Webhooks

For webhooks you first need to add url where you want the payload inside the webhook section of integeration in admin panel. There are three types of webhooks

  1. Status Webhook: Will hit any time the status is changed of verification.
  2. Decision Webhook: Will hit only if decision is made either approved or rejected.
  3. Proof Address Webhook: Will hit for the decision if document type is proof of address.

Following is the payload you will get with all the webhook types. "uniqueIdentifier" will have the "unique_identifier" sent during the creation of verificaiton

    
    id: string, //verification id to be saved
    integrationId: string,
    status: VerificationStatusEnum,
    uniqueIdentifier: string,
    documentType?: VerificationDocumentEnum | null,
    requestUrl: string,
    hasError?: boolean,
    phoneNumberVerified: boolean,
    verifiedPhoneNumber: string,
    documentBack: {url: string},
    documentFront: {url: string},
    verificationDetails: {
        firstName: string,
        lastName: string,
        dateOfBirth: string | null,
        gender: string | null,
        idNumber: string | null,
        nationality: string | null,
        placeOfBirth: string | null,
        name: string | null,
        surname: string | null,
        expiration_date: string | null,
        address: string | null,
        country: string | null,
    }
    reason: string | null,

VerificationStatusEnum

    Not Started,
    Started,
    Submitted,
    Expired,
    Abandoned,
    Declined,
    Approved,
    Under Review,

VerificationDocumentEnum

    Passport,
    License,
    Address Permit,
    Proof Address,

Webhooks Implementation Example

Following is the example for how to implement the webhooks

    
        import {
            Body,
            Controller,
            Inject,
            Post,
            Logger,
            HttpCode,
            HttpStatus,
        } from '@nestjs/common';
        import { ClientRMQ } from '@nestjs/microservices';
        import { MESSAGE_PATTERNS, SERVICES } from '../constants';
        import { firstValueFrom } from 'rxjs';
        import { ApiExcludeController } from '@nestjs/swagger';
        
        // Enums
        enum VerificationStatus {
            Not_Started = "Not Started",
            Started = "Started",
            Submitted = "Submitted",
            Expired = "Expired",
            Abandoned = "Abandoned",
            Declined = "Declined",
            Approved = "Approved",
            UnderReview = "Under Review",
        }
        
        export enum VerificationDocument {
            Passport = "Passport",
            License = "License",
            Address_Permit = "Address Permit",
            Proof_Address = "Proof Address",
        }
        
        // Webhook Request Format
        export class WebhookRequestDto {
            id: string, //verification id to be saved
            integrationId: string,
            status: VerificationStatusEnum,
            uniqueIdentifier: string,
            documentType?: VerificationDocumentEnum | null,
            requestUrl: string,
            hasError?: boolean,
            phoneNumberVerified: boolean,
            verifiedPhoneNumber: string,
            documentBack: {url: string},
            documentFront: {url: string},
            verificationDetails: {
                firstName: string,
                lastName: string,
                dateOfBirth: string | null,
                gender: string | null,
                idNumber: string | null,
                nationality: string | null,
                placeOfBirth: string | null,
                name: string | null,
                surname: string | null,
                expiration_date: string | null,
                address: string | null,
                country: string | null,
            }
            reason: string | null,
        }
        
        const {
            ADMIN_USER: { ADMIN_CONFIRM_SIGNUP },
        } = MESSAGE_PATTERNS.USER_ACCOUNT;
        
        const {
            USER: { UPDATE_USER },
        } = MESSAGE_PATTERNS.USER_PROFILE;
        
        @ApiExcludeController()
        @Controller('webhooks')
        export class WebhookController {
            private readonly logger = new Logger(WebhookController.name);
            constructor(
                @Inject(SERVICES.USER_ACCOUNT) private authClient: ClientRMQ,
                @Inject(SERVICES.USER_PROFILE) private userClient: ClientRMQ,
            ) {}
        
            @Post('status')
            @HttpCode(HttpStatus.OK)
            async status(@Body() dto: WebhookRequestDto) {
                // Signed up for user as documents are submitted which means that it was a verified email
                if (dto.status === VerificationStatus.Submitted) {
                    await firstValueFrom(
                        this.authClient.send(ADMIN_CONFIRM_SIGNUP, {
                            userId: dto.uniqueIdentifier,
                        }),
                    );
                }
            
                await firstValueFrom(
                    this.userClient.send(UPDATE_USER, {
                        userId: dto.uniqueIdentifier,
                        idVerificationStatus: dto.status,
                        // Email verified and active
                        ...(dto.status === VerificationStatus.Submitted && {
                            emailVerified: new Date(),
                            isActive: true,
                        }),
                    }),
                );
                return {
                    data: null,
                    message: 'Integration client has received the status.',
                    errors: null,
                };
            }
        
            @Post('decision')
            @HttpCode(HttpStatus.OK)
            async decision(@Body() dto: WebhookRequestDto) {
                // User Syncing
                await firstValueFrom(
                    this.userClient.send(UPDATE_USER, {
                        userId: dto.uniqueIdentifier,
                        idVerified: dto.status === VerificationStatus.Approved,
                        idVerificationStatus: dto.status,
                    }),
                );
                return {
                    data: null,
                    message: `Integration client has received the decision.`,
                    errors: null,
                };
            }
        
            @Post('proof-of-address')
            @HttpCode(HttpStatus.OK)
            async proofOfAddress(@Body() dto: WebhookRequestDto) {
                // Scenario goes here
                return {
                    data: null,
                    message: `Integration client has received the decision.`,
                    errors: null,
                };
            }
        }