Introduction


To utilize the KYC (Know Your Customer) service, you must first register the user using the RegisterUser service. Once the user is registered with their nationalID, you can proceed to use the KYC service by providing the registered nationalID along with a video. The system will then process the information and provide the verification result.

RegisterUser


The RegisterUser endpoint allows users to upload an image along with their national code. This image is then stored and can be used for further verification processes.

HTTP POST Request

POST https://gapi.presentid.com/api/UserImage/saveImage
            

Parameters

Example Request

[HttpPost]
public async Task SaveImage([FromForm] string nationalCode, [FromForm] IFormFile userImageFile)
{
    if (string.IsNullOrEmpty(nationalCode) || userImageFile == null || userImageFile.Length == 0)
    {
        return BadRequest(new { message = "National code and image file are required." });
    }

    try
    {
        string base64Image;
        using (var memoryStream = new MemoryStream())
        {
            await userImageFile.CopyToAsync(memoryStream);
            base64Image = Convert.ToBase64String(memoryStream.ToArray());
        }

        var requestData = new { NationalCode = nationalCode, Base64Image = base64Image };
        var content = new StringContent(JsonConvert.SerializeObject(requestData), Encoding.UTF8, "application/json");

        var request = new HttpRequestMessage
        {
            Method = HttpMethod.Post,
            RequestUri = new Uri("https://gapi.presentid.com/api/UserImage/saveImage"),
            Content = content,
            Headers = { { "Authorization", $"Bearer {BearerToken}" } }
        };

        var response = await _httpClient.SendAsync(request);
        var responseString = await response.Content.ReadAsStringAsync();

        Log.Information("SaveImage Response: {Response}", responseString);

        if (!response.IsSuccessStatusCode)
        {
            return StatusCode((int)response.StatusCode, new { message = "Error saving image." });
        }

        return Ok(responseString);
    }
    catch (Exception ex)
    {
        Log.Error(ex, "Error occurred during SaveImage.");
        return StatusCode(500, $"Internal server error: {ex.Message}");
    }
}
            

Code Breakdown

1. Method Annotation

[HttpPost]
: Indicates that this method handles HTTP POST requests.

2. Method Signature

public async Task SaveImage([FromForm] string nationalCode, [FromForm] IFormFile userImageFile)
: Defines an asynchronous method that returns an
IActionResult
. It accepts two parameters: "nationalCode" and "userImageFile", both sent via form data.

3. Input Validation

if (string.IsNullOrEmpty(nationalCode) || userImageFile == null || userImageFile.Length == 0)
{
    return BadRequest(new { message = "National code and image file are required." });
}
                    
Purpose: Ensures that both the national code and image file are provided.
Action: Returns a 400 Bad Request response if validation fails.

4. Converting Image to Base64

string base64Image;
using (var memoryStream = new MemoryStream())
{
    await userImageFile.CopyToAsync(memoryStream);
    base64Image = Convert.ToBase64String(memoryStream.ToArray());
}
                    
Purpose: Converts the uploaded image file to a Base64 string for transmission.

5. Preparing JSON Payload

var requestData = new { NationalCode = nationalCode, Base64Image = base64Image };
var content = new StringContent(JsonConvert.SerializeObject(requestData), Encoding.UTF8, "application/json");
                    
Purpose: Creates a JSON payload containing the national code and Base64-encoded image.

6. Creating the HTTP Request

var request = new HttpRequestMessage
{
    Method = HttpMethod.Post,
    RequestUri = new Uri("https://gapi.presentid.com/api/UserImage/saveImage"),
    Content = content,
    Headers = { { "Authorization", $"Bearer {BearerToken}" } }
};
                    
Purpose: Constructs an HTTP POST request to the "saveImage" endpoint with the JSON payload and authorization header.

7. Sending the Request

var response = await _httpClient.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
                    
Purpose: Sends the HTTP request asynchronously and reads the response content as a string.

8. Logging the Response

Log.Information("SaveImage Response: {Response}", responseString);
                    
Purpose: Logs the server's response for debugging and monitoring purposes.

9. Handling Non-Success Status Codes

if (!response.IsSuccessStatusCode)
{
    return StatusCode((int)response.StatusCode, new { message = "Error saving image." });
}
                    
Purpose: Checks if the response indicates a successful request.
Action: If not successful, returns a response with the corresponding HTTP status code and an error message.

10. Returning Success Response

return Ok(responseString);
                    
Purpose: Returns a 200 OK response with the server's response content if the request was successful.

11. Exception Handling

catch (Exception ex)
{
    Log.Error(ex, "Error occurred during SaveImage.");
    return StatusCode(500, $"Internal server error: {ex.Message}");
}
                    
Purpose: Catches any exceptions that occur during the request processing.
Action:
  • Logs the exception details.
  • Returns a 500 Internal Server Error response with the exception message.

Responses

Successful Upload:

HTTP 200 OK
Content: { "message": "Video uploaded successfully." }
            

Request Failure:

HTTP 400 Bad Request
Content: { "message": "National code and image file are required." }
            

Internal Server Error:

HTTP 500 Internal Server Error
Content: { "message": "Internal server error: [error description]" }
            

Liveness Detection


The Liveness Detection endpoint verifies that the video provided by the user displays real human activity, confirming the presence and authenticity of the user at the time the video was recorded.

HTTP POST Request

POST https://gapi.presentid.com/LivenessDetection
            

Parameters

Example Request

[HttpPost("LivenessDetection")]
public async Task LivenessDetection([FromForm] IFormFile video)
{
    if (video == null || video.Length == 0)
    {
        Log.Warning("LivenessDetection: No video file provided.");
        return BadRequest(new { message = "Video file is required." });
    }

    try
    {
        using var content = new MultipartFormDataContent();
        content.Add(new StreamContent(video.OpenReadStream()), "video", video.FileName);

        var request = new HttpRequestMessage
        {
            Method = HttpMethod.Post,
            RequestUri = new Uri("https://gapi.presentid.com/LivenessDetection"),
            Content = content,
            Headers = { { "Authorization", $"Bearer {BearerToken}" } }
        };

        var response = await _httpClient.SendAsync(request);
        var responseString = await response.Content.ReadAsStringAsync();
        var responseObject = JsonConvert.DeserializeObject(responseString);

        Log.Information("LivenessDetection Response: {Response}", responseString);

        if (!response.IsSuccessStatusCode)
        {
            return StatusCode((int)response.StatusCode, new { message = responseObject?.message ?? "Error occurred." });
        }

        return Ok(responseString);
    }
    catch (Exception ex)
    {
        Log.Error(ex, "Error occurred during LivenessDetection.");
        return StatusCode(500, new { message = $"Internal server error: {ex.Message}" });
    }
}
            

Code Breakdown

1. Method Annotation

[HttpPost("LivenessDetection")]
: Specifies that this method handles HTTP POST requests sent to the "LivenessDetection" endpoint.

2. Method Signature

public async Task LivenessDetection([FromForm] IFormFile video)
: Defines an asynchronous method that returns an
IActionResult
. It accepts a single parameter, "video", which is an uploaded file sent via form data.

3. Input Validation

if (video == null || video.Length == 0)
{
    Log.Warning("LivenessDetection: No video file provided.");
    return BadRequest(new { message = "Video file is required." });
}
                    
Purpose: Checks if the video file is present and not empty.
Action: Logs a warning and returns a 400 Bad Request response if validation fails.

4. Try-Catch Block

Encloses the main logic to handle potential exceptions that may occur during the processing of the request.

5. Preparing the Request Content

using var content = new MultipartFormDataContent();
content.Add(new StreamContent(video.OpenReadStream()), "video", video.FileName);
                    
Purpose: Creates multipart form data content to send the video file.
Action: Adds the video file stream to the content with the key "video" and the original file name.

6. Creating the HTTP Request

var request = new HttpRequestMessage
{
    Method = HttpMethod.Post,
    RequestUri = new Uri("https://gapi.presentid.com/LivenessDetection"),
    Content = content,
    Headers = { { "Authorization", $"Bearer {BearerToken}" } }
};
                    
Purpose: Constructs an HTTP POST request to the specified URI.
Action:
  • Sets the HTTP method to POST.
  • Specifies the request URI.
  • Attaches the prepared content.
  • Adds an Authorization header with a bearer token for authentication.

7. Sending the Request

var response = await _httpClient.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
var responseObject = JsonConvert.DeserializeObject(responseString);
                    
Purpose: Sends the HTTP request and processes the response.
Action:
  • Sends the request asynchronously.
  • Reads the response content as a string.
  • Deserializes the JSON response into a dynamic object for easier access to its properties.

8. Logging the Response

Log.Information("LivenessDetection Response: {Response}", responseString);
                    
Purpose: Logs the server's response for debugging and monitoring purposes.

9. Handling Non-Success Status Codes

if (!response.IsSuccessStatusCode)
{
    return StatusCode((int)response.StatusCode, new { message = responseObject?.message ?? "Error occurred." });
}
                    
Purpose: Checks if the response indicates a successful request.
Action: If not successful, returns a response with the corresponding HTTP status code and an error message.

10. Returning Success Response

return Ok(responseString);
                    
Purpose: Returns a 200 OK response with the server's response content if the request was successful.

11. Exception Handling

catch (Exception ex)
{
    Log.Error(ex, "Error occurred during LivenessDetection.");
    return StatusCode(500, new { message = $"Internal server error: {ex.Message}" });
}
                    
Purpose: Catches any exceptions that occur during the request processing.
Action:
  • Logs the exception details.
  • Returns a 500 Internal Server Error response with the exception message.

Responses

Successful Processing:

HTTP 200 OK
Content: { "message": "Liveness verified successfully." }
            

Request Failure:

HTTP 400 Bad Request
Content: { "message": "Video file is required." }
            

Internal Server Error:

HTTP 500 Internal Server Error
Content: { "message": "Internal server error: [error description]" }
            

Face Verification


The Face Verification endpoint compares two images to verify if they belong to the same individual. This is essential for confirming the authenticity of the user's identity.

HTTP POST Request

POST https://gapi.presentid.com/FaceVerification
            

Parameters

Example Request

[HttpPost]
public async Task Verification([FromForm] IFormFile image1, [FromForm] IFormFile image2)
{
    if (image1 == null || image2 == null || image1.Length == 0 || image2.Length == 0)
    {
        Log.Warning("FaceVerification: One or both images are missing.");
        return BadRequest(new { message = "Both images are required." });
    }

    try
    {
        using var content = new MultipartFormDataContent();
        content.Add(new StreamContent(image1.OpenReadStream()), "photo1", image1.FileName);
        content.Add(new StreamContent(image2.OpenReadStream()), "photo2", image2.FileName);

        var request = new HttpRequestMessage
        {
            Method = HttpMethod.Post,
            RequestUri = new Uri("https://gapi.presentid.com/FaceVerification"),
            Content = content,
            Headers = { { "Authorization", $"Bearer {BearerToken}" } }
        };

        var response = await _httpClient.SendAsync(request);
        var responseString = await response.Content.ReadAsStringAsync();
        var responseObject = JsonConvert.DeserializeObject(responseString);

        Log.Information("FaceVerification Response: {Response}", responseString);

        if (!response.IsSuccessStatusCode)
        {
            return StatusCode((int)response.StatusCode, new { message = responseObject?.message ?? "Error occurred." });
        }

        return Ok(responseString);
    }
    catch (Exception ex)
    {
        Log.Error(ex, "Error occurred during FaceVerification.");
        return StatusCode(500, new { message = $"Internal server error: {ex.Message}" });
    }
}
            

Code Breakdown

1. Method Annotation

[HttpPost]
: Indicates that this method handles HTTP POST requests.

2. Method Signature

public async Task Verification([FromForm] IFormFile image1, [FromForm] IFormFile image2)
: Defines an asynchronous method that returns an
IActionResult
. It accepts two parameters, "image1" and "image2", which are uploaded files sent via form data.

3. Input Validation

if (image1 == null || image2 == null || image1.Length == 0 || image2.Length == 0)
{
    Log.Warning("FaceVerification: One or both images are missing.");
    return BadRequest(new { message = "Both images are required." });
}
                    
Purpose: Ensures that both image files are provided and not empty.
Action: Logs a warning and returns a 400 Bad Request response if validation fails.

4. Try-Catch Block

Encloses the main logic to handle potential exceptions that may occur during the processing of the request.

5. Preparing the Request Content

using var content = new MultipartFormDataContent();
content.Add(new StreamContent(image1.OpenReadStream()), "photo1", image1.FileName);
content.Add(new StreamContent(image2.OpenReadStream()), "photo2", image2.FileName);
                    
Purpose: Creates multipart form data content to send both image files.
Action: Adds both image file streams to the content with keys "photo1" and "photo2" respectively.

6. Creating the HTTP Request

var request = new HttpRequestMessage
{
    Method = HttpMethod.Post,
    RequestUri = new Uri("https://gapi.presentid.com/FaceVerification"),
    Content = content,
    Headers = { { "Authorization", $"Bearer {BearerToken}" } }
};
                    
Purpose: Constructs an HTTP POST request to the "FaceVerification" endpoint with the image files and authorization header.

7. Sending the Request

var response = await _httpClient.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
var responseObject = JsonConvert.DeserializeObject(responseString);
                    
Purpose: Sends the HTTP request and processes the response.
Action:
  • Sends the request asynchronously.
  • Reads the response content as a string.
  • Deserializes the JSON response into a dynamic object.

8. Logging the Response

Log.Information("FaceVerification Response: {Response}", responseString);
                    
Purpose: Logs the server's response for debugging and monitoring purposes.

9. Handling Non-Success Status Codes

if (!response.IsSuccessStatusCode)
{
    return StatusCode((int)response.StatusCode, new { message = responseObject?.message ?? "Error occurred." });
}
                    
Purpose: Checks if the response indicates a successful request.
Action: If not successful, returns a response with the corresponding HTTP status code and an error message.

10. Returning Success Response

return Ok(responseString);
                    
Purpose: Returns a 200 OK response with the server's response content if the request was successful.

11. Exception Handling

catch (Exception ex)
{
    Log.Error(ex, "Error occurred during FaceVerification.");
    return StatusCode(500, new { message = $"Internal server error: {ex.Message}" });
}
                    
Purpose: Catches any exceptions that occur during the request processing.
Action:
  • Logs the exception details.
  • Returns a 500 Internal Server Error response with the exception message.

Responses

Successful Verification:

HTTP 200 OK
Content: { "message": "Verification successful.", "isMatch": true }
            

Request Failure:

HTTP 400 Bad Request
Content: { "message": "Both images are required." }
            

Internal Server Error:

HTTP 500 Internal Server Error
Content: { "message": "Internal server error: [error description]" }
            

KYC


The KYC endpoint allows users to upload a video along with their national code. This video is then used in conjunction with other services such as liveness detection and face verification.

HTTP POST Request

POST https://gapi.presentid.com/IQAandFaceVerification
            

Parameters

Example Request

[HttpPost]
public async Task UploadVideo([FromForm] IFormFile video, [FromForm] string nationalCode)
{
    if (video == null || video.Length == 0)
    {
        Log.Warning("UploadVideo: Video file is missing.");
        return BadRequest(new { message = "Video file is missing." });
    }

    try
    {
        using var content = new MultipartFormDataContent();
        content.Add(new StreamContent(video.OpenReadStream()), "video", video.FileName);
        content.Add(new StringContent(nationalCode), "nationalCode");

        var request = new HttpRequestMessage
        {
            Method = HttpMethod.Post,
            RequestUri = new Uri("https://gapi.presentid.com/IQAandFaceVerification"),
            Content = content,
            Headers = { { "Authorization", $"Bearer {BearerToken}" } }
        };

        var response = await _httpClient.SendAsync(request);
        var responseString = await response.Content.ReadAsStringAsync();

        Log.Information("UploadVideo Response: {Response}", responseString);

        if (!response.IsSuccessStatusCode)
        {
            return StatusCode((int)response.StatusCode, new { message = "Error uploading video." });
        }

        return Ok(responseString);
    }
    catch (Exception ex)
    {
        Log.Error(ex, "Error uploading video.");
        return StatusCode(500, $"Internal server error: {ex.Message}");
    }
}
            

Code Breakdown

1. Method Annotation

[HttpPost]
: Indicates that this method handles HTTP POST requests.

2. Method Signature

public async Task UploadVideo([FromForm] IFormFile video, [FromForm] string nationalCode)
: Defines an asynchronous method that returns an
IActionResult
. It accepts two parameters: "video" (an uploaded file) and "nationalCode" (a string), both sent via form data.

3. Input Validation

if (video == null || video.Length == 0)
{
    Log.Warning("UploadVideo: Video file is missing.");
    return BadRequest(new { message = "Video file is missing." });
}
                    
Purpose: Ensures that the video file is provided and not empty.
Action: Logs a warning and returns a 400 Bad Request response if validation fails.

4. Try-Catch Block

Encloses the main logic to handle potential exceptions that may occur during the processing of the request.

5. Preparing the Request Content

using var content = new MultipartFormDataContent();
content.Add(new StreamContent(video.OpenReadStream()), "video", video.FileName);
content.Add(new StringContent(nationalCode), "nationalCode");
                    
Purpose: Creates multipart form data content to send both the video file and the national code.
Action: Adds the video file stream and the national code to the content with appropriate keys.

6. Creating the HTTP Request

var request = new HttpRequestMessage
{
    Method = HttpMethod.Post,
    RequestUri = new Uri("https://gapi.presentid.com/IQAandFaceVerification"),
    Content = content,
    Headers = { { "Authorization", $"Bearer {BearerToken}" } }
};
                    
Purpose: Constructs an HTTP POST request to the "IQAandFaceVerification" endpoint with the video file, national code, and authorization header.

7. Sending the Request

var response = await _httpClient.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
                    
Purpose: Sends the HTTP request asynchronously and reads the response content as a string.

8. Logging the Response

Log.Information("UploadVideo Response: {Response}", responseString);
                    
Purpose: Logs the server's response for debugging and monitoring purposes.

9. Handling Non-Success Status Codes

if (!response.IsSuccessStatusCode)
{
    return StatusCode((int)response.StatusCode, new { message = "Error uploading video." });
}
                    
Purpose: Checks if the response indicates a successful request.
Action: If not successful, returns a response with the corresponding HTTP status code and an error message.

10. Returning Success Response

return Ok(responseString);
                    
Purpose: Returns a 200 OK response with the server's response content if the request was successful.

11. Exception Handling

catch (Exception ex)
{
    Log.Error(ex, "Error uploading video.");
    return StatusCode(500, $"Internal server error: {ex.Message}");
}
                    
Purpose: Catches any exceptions that occur during the request processing.
Action:
  • Logs the exception details.
  • Returns a 500 Internal Server Error response with the exception message.

Responses

Successful Upload:

HTTP 200 OK
Content: { "message": "Video uploaded successfully." }
            

Request Failure:

HTTP 400 Bad Request
Content: { "message": "Video file is missing." }
            

Internal Server Error:

HTTP 500 Internal Server Error
Content: { "message": "Internal server error: [error description]" }
            

Front

The following code demonstrates the front-end implementation for the KYC (Know Your Customer) process. This implementation allows users to enter their national code, access their camera, perform face detection, record a video, and submit the data to the server.

Front-End Code

<link href="~/css/site.css" rel="stylesheet" />  

<div class="relative" style="margin-top: 100px">
    <h1 class="text-3xl m-4 text-gray-600" style="text-align: center">Know Your Customer</h1>
    <div class="relative p-4">

        <form id="uploadForm" style="text-align: center">
            <div class="mb-4 pt-0 flex flex-col">
                <label class="mb-2 text-gray-800 text-lg font-light" for="nationalCode">National Code:</label>
                <input type="text" id="nationalCode" name="nationalCode" class="border-2 rounded h-10 px-6 text-lg text-gray-600 focus:outline-none focus:ring focus:border-blue-300" autocomplete="off" required style="width: 300px; margin: auto">
            </div>

            <div id="videoContainer">
                <div id="feedback"></div>
                <video id="videotoPreview" autoplay muted></video>
                <div class="overlay border-red"></div>
                <div class="red-dot" id="redDot"></div>
            </div>

            <button type="button" class="bg-yellow-500 text-white p-5 h-16 rounded-lg font-bold" id="openCamera">Open Camera</button>
            <button type="button" class="bg-green-500 text-white p-5 h-16 rounded-lg font-bold" id="startRecording" disabled>Start Recording</button>
            <button type="button" class="hidden" id="stopRecording" disabled>Stop Recording</button>
            <p id="timer">Time Left: <span id="timerValue">5</span> seconds</p>
            <br><br>

            <input type="hidden" id="videoBlob" name="videoBlob">
            <button type="submit" class="bg-blue-500 p-5 text-white h-16 rounded-lg font-bold">Submit</button>
            <p id="loading">Submitting... Please wait.</p>
            <div id="responseMessage" style="margin-top: 30px"></div>
        </form>
    </div>
</div>

<script src="~/js/tfjs.js"></script>
<script src="~/js/blazeface.js"></script>

<script>
    const openCameraButton = document.getElementById('openCamera');
    const videotoPreview = document.getElementById('videotoPreview');
    const startRecordingButton = document.getElementById('startRecording');
    const stopRecordingButton = document.getElementById('stopRecording');
    const uploadForm = document.getElementById('uploadForm'); // Added declaration
    const responseMessage = document.getElementById('responseMessage');
    const loading = document.getElementById('loading');
    const timer = document.getElementById('timer');
    const timerValue = document.getElementById('timerValue');
    const redDot = document.getElementById('redDot');

    let mediaRecorder;
    let recordedChunks = [];
    let countdown;
    let stream; // Added declaration

    openCameraButton.addEventListener('click', async () => {
        try {
            stream = await navigator.mediaDevices.getUserMedia({ video: true });
            videotoPreview.srcObject = stream;

            startRecordingButton.disabled = false;
            openCameraButton.disabled = true;

            console.log('Camera opened successfully.');

            const model = await blazeface.load(); // Removed modelUrl if not necessary
            console.log('BlazeFace model loaded.');

            detectFaces(model);
        } catch (err) {
            console.error('Error accessing camera:', err);
            alert('Could not access the camera. Please check your device settings.');
        }
    });

    async function detectFaces(model) {
        const canvas = document.createElement('canvas');
        const context = canvas.getContext('2d');

        canvas.width = videotoPreview.videoWidth || 400;
        canvas.height = videotoPreview.videoHeight || 300;

        const feedbackMessage = document.getElementById('feedback');
        feedbackMessage.style.position = 'absolute';
        feedbackMessage.style.color = 'white';
        feedbackMessage.style.fontSize = '15px';
        feedbackMessage.style.width = '100%';
        feedbackMessage.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
        feedbackMessage.style.padding = '10px';
        feedbackMessage.style.borderRadius = '5px';
        feedbackMessage.style.zIndex = '5';
        feedbackMessage.innerText = '';

        const overlay = document.querySelector('.overlay');

        const ellipseX = canvas.width / 2;
        const ellipseY = canvas.height / 2;
        const ellipseWidth = 139;
        const ellipseHeight = 177;

        const minFaceWidth = 150;
        const maxFaceWidth = 250;

        setInterval(async () => {
            context.drawImage(videotoPreview, 0, 0, canvas.width, canvas.height);
            const predictions = await model.estimateFaces(canvas, false);

            if (predictions.length > 0) {
                const face = predictions[0];
                const startX = face.topLeft[0];
                const startY = face.topLeft[1];
                const endX = face.bottomRight[0];
                const endY = face.bottomRight[1];

                const faceWidth = endX - startX;
                const faceHeight = endY - startY;
                const faceCenterX = (startX + endX) / 2;
                const faceCenterY = (startY + endY) / 2;

                const normalizedX = (faceCenterX - ellipseX) / ellipseWidth;
                const normalizedY = (faceCenterY - ellipseY) / ellipseHeight;
                const isInsideEllipse = (normalizedX ** 2 + normalizedY ** 2) <= 1;

                const isFaceSizeValid = faceWidth >= minFaceWidth && faceWidth <= maxFaceWidth;

                if (isInsideEllipse && isFaceSizeValid) {
                    redDot.style.backgroundColor = 'green';
                    overlay.classList.add('border-green');
                    overlay.classList.remove('border-red');
                    feedbackMessage.innerText = 'Face detected and positioned correctly!';
                } else {
                    redDot.style.backgroundColor = 'red';
                    overlay.classList.add('border-red');
                    overlay.classList.remove('border-green');

                    let feedback = 'Adjust your position: ';
                    if (!isInsideEllipse) {
                        if (faceCenterX < ellipseX) feedback += 'Move left, ';
                        if (faceCenterX > ellipseX) feedback += 'Move right, ';
                        if (faceCenterY < ellipseY) feedback += 'Move down, ';
                        if (faceCenterY > ellipseY) feedback += 'Move up, ';
                    }
                    if (!isFaceSizeValid) {
                        if (faceWidth < minFaceWidth) feedback += 'Move closer.';
                        if (faceWidth > maxFaceWidth) feedback += 'Move farther.';
                    }

                    feedbackMessage.innerText = feedback.trim().replace(/,\s*$/, '.');
                }
            } else {
                redDot.style.backgroundColor = 'red';
                overlay.classList.add('border-red');
                overlay.classList.remove('border-green');
                feedbackMessage.innerText = 'No face detected. Please position yourself in front of the camera.';
            }
        }, 100);
    }

    startRecordingButton.addEventListener('click', () => {
        if (!stream) {
            alert('Camera is not open.');
            return;
        }
        recordedChunks = [];
        mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm' }); // Specified MIME type
        mediaRecorder.ondataavailable = event => {
            if (event.data.size > 0) {
                recordedChunks.push(event.data);
            }
        };
        mediaRecorder.start();
        startRecordingButton.disabled = true;
        stopRecordingButton.disabled = false;
        timer.style.display = 'block';
        redDot.style.display = 'block';

        let timeLeft = 5;
        timerValue.textContent = timeLeft;

        countdown = setInterval(() => {
            timeLeft -= 1;
            timerValue.textContent = timeLeft;

            if (timeLeft <= 0) {
                clearInterval(countdown);
                mediaRecorder.stop();
                timer.style.display = 'none';
                redDot.style.display = 'none';
                startRecordingButton.disabled = false;
                stopRecordingButton.disabled = true;
                console.log('Recording stopped automatically after 5 seconds.');
            }
        }, 1000);
    });

    stopRecordingButton.addEventListener('click', () => {
        clearInterval(countdown);
        mediaRecorder.stop();
        timer.style.display = 'none';
        redDot.style.display = 'none';
        startRecordingButton.disabled = false;
        stopRecordingButton.disabled = true;
    });

    uploadForm.addEventListener('submit', async (event) => {
        event.preventDefault();

        const nationalCode = document.getElementById('nationalCode').value;

        if (!nationalCode) {
            responseMessage.textContent = 'Please enter your national code.';
            responseMessage.style.color = 'red';
            return;
        }

        if (recordedChunks.length === 0) {
            responseMessage.textContent = 'Please record a video before submitting.';
            responseMessage.style.color = 'red';
            return;
        }

        const blob = new Blob(recordedChunks, { type: 'video/webm' }); // Changed to match MIME type
        const formData = new FormData();
        formData.append('nationalCode', nationalCode);
        formData.append('video', blob, 'recorded_video.webm'); // Changed extension to match MIME type

        loading.style.display = 'block';

        try {
            const response = await fetch('/Home/UploadVideo', { // Updated URL to a placeholder
                method: 'POST',
                body: formData,
            });

            const result = await response.json();
            if (response.ok) {
                responseMessage.innerHTML = `<pre class="text-green-700" style="color: rgb(60 255 0); background: #000; text-align: left; overflow: auto;">${JSON.stringify(result, null, 2)}</pre>`;
            } else {
                responseMessage.innerHTML = `<p class='text-red-500'>Error: ${result.message || "An unknown error occurred."}</p>`;
            }
        } catch (err) {
            console.error('Error sending video:', err);
            responseMessage.textContent = 'Error occurred while sending the video.';
            responseMessage.style.color = 'red';
        } finally {
            loading.style.display = 'none';
        }
    });
</script>

VerifyVideo API


The VerifyVideo API processes a video and an image to verify if they belong to the same person. Note: A valid Bearer Token must be included in the request header as Authorization: Bearer <token>.

Workflow:

  1. Receive Files: The API accepts two files via multipart form data:
    • video: The video file capturing the user's actions.
    • image: The image file of the user.
  2. Liveness Detection: The API calls the LivenessDetection service to check if the video is live (not spoofed).
  3. Face Verification: The extracted frame and the original image are sent to the FaceVerification service to compare the faces.

HTTP POST Request

POST https://gapi.presentid.com/verifyvideo
    

Headers

Input Parameters

Expected Output

The response is a JSON object with the following structure:

{
  "statusCode": 200,
  "statusMessage": "OK",
  "data": {
    "resultIndex": 3,
    "resultMessage": "The two faces belong to different people.",
    "similarPercent": 0.5345810558997499
  }
}
    

Interpretation of resultIndex:

Your application should determine the final verification outcome based on the resultIndex value.