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');
    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;

    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();
            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' });
        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' });
        const formData = new FormData();
        formData.append('nationalCode', nationalCode);
        formData.append('video', blob, 'recorded_video.webm');

        loading.style.display = 'block';

        try {
            const response = await fetch('/Home/UploadVideo', {
                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.

FaceCrop API


The FaceCrop API processes an image to detect and crop the region containing a face, returning the cropped image in PNG format. Note: A valid Bearer Token must be included in the request header as Authorization: Bearer <token>.

Workflow:

  1. Receive Image: The API accepts an image file via multipart form data:
    • image: The image file (JPG, JPEG, or PNG) containing a face.
  2. Face Detection: The API sends the image to an external face detection service (https://api.hifacy.com/facedetection) to retrieve the coordinates of the detected face(s).
  3. Cropping: The API crops the region of the first detected face using the coordinates provided by the face detection service and converts the output to PNG format.

HTTP POST Request

POST https://gapi.presentid.com/api/facecrop
            

Headers

Input Parameters

Expected Output

The response is a binary image file in PNG format containing the cropped face region.

Content-Type: image/png
Content-Disposition: attachment; filename="cropped_face.png"
            

Error Responses:

Sample Code

cURL:

curl -X POST "https://gapi.presentid.com/api/facecrop" \
-H "Authorization: Bearer <your-token>" \
-F "image=@/path/to/image.jpg" \
-o cropped_face.png
            

Python (using requests):

import requests

url = "https://gapi.presentid.com/api/facecrop"
headers = {"Authorization": "Bearer <your-token>"}
files = {"image": open("/path/to/image.jpg", "rb")}

response = requests.post(url, headers=headers, files=files)

if response.status_code == 200:
    with open("cropped_face.png", "wb") as f:
        f.write(response.content)
else:
    print(f"Error: {response.json()}")
            

Notes

Speaker Verification


The Speaker Verification API compares two audio files to verify if they belong to the same speaker. This is essential for confirming the authenticity of a user's identity through voice recognition. Note: A valid Bearer Token must be included in the request header as Authorization: Bearer <token>.

Workflow:

  1. Receive Audio Files: The API accepts two audio files via multipart form data:
    • sound1: The first audio file containing a speaker's voice.
    • sound2: The second audio file containing a speaker's voice.
  2. Speaker Analysis: The API processes the audio files to extract voice features and compares them to determine if they belong to the same speaker.

HTTP POST Request

POST https://gapi.presentid.com/api/speakerverification
            

Headers

Input Parameters

Expected Output

The response is a JSON object with the following structure:

{
    "data": {
        "similarityLevel": 0,
        "similarityMessage": "Absolutely Same",
        "similarityPercent": "1.0"
    },
    "hasError": false,
    "statusCode": 200,
    "statusMessage": "OK"
}
            

Interpretation of similarityLevel:

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

Example Request

[HttpPost]
public async Task SpeakerVerification([FromForm] IFormFile sound1, [FromForm] IFormFile sound2)
{
    if (sound1 == null || sound2 == null || sound1.Length == 0 || sound2.Length == 0)
    {
        Log.Warning("SpeakerVerification: One or both audio files are missing.");
        return BadRequest(new { message = "Both audio files are required." });
    }

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

        var request = new HttpRequestMessage
        {
            Method = HttpMethod.Post,
            RequestUri = new Uri("https://gapi.presentid.com/api/speakerverification"),
            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("SpeakerVerification 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 SpeakerVerification.");
        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 SpeakerVerification([FromForm] IFormFile sound1, [FromForm] IFormFile sound2)
: Defines an asynchronous method that returns an
IActionResult
. It accepts two parameters, "sound1" and "sound2", which are uploaded audio files sent via form data.

3. Input Validation

if (sound1 == null || sound2 == null || sound1.Length == 0 || sound2.Length == 0)
{
    Log.Warning("SpeakerVerification: One or both audio files are missing.");
    return BadRequest(new { message = "Both audio files are required." });
}
                    
Purpose: Ensures that both audio 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(sound1.OpenReadStream()), "sound1", sound1.FileName);
content.Add(new StreamContent(sound2.OpenReadStream()), "sound2", sound2.FileName);
                    
Purpose: Creates multipart form data content to send both audio files.
Action: Adds both audio file streams to the content with keys "sound1" and "sound2" respectively.

6. Creating the HTTP Request

var request = new HttpRequestMessage
{
    Method = HttpMethod.Post,
    RequestUri = new Uri("https://gapi.presentid.com/api/speakerverification"),
    Content = content,
    Headers = { { "Authorization", $"Bearer {BearerToken}" } }
};
                    
Purpose: Constructs an HTTP POST request to the "speakerverification" endpoint with the audio 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("SpeakerVerification 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 SpeakerVerification.");
    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: {
    "data": {
        "similarityLevel": 0,
        "similarityMessage": "Absolutely Same",
        "similarityPercent": "1.0"
    },
    "hasError": false,
    "statusCode": 200,
    "statusMessage": "OK"
}
            

Request Failure:

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

Internal Server Error:

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

Sample Code

cURL:

curl -X POST "https://gapi.presentid.com/api/speakerverification" \
-H "Authorization: Bearer <your-token>" \
-F "sound1=@/path/to/audio1.wav" \
-F "sound2=@/path/to/audio2.wav"
            

Python (using requests):

import requests

url = "https://gapi.presentid.com/api/speakerverification"
headers = {"Authorization": "Bearer <your-token>"}
files = {
    "sound1": open("/path/to/audio1.wav", "rb"),
    "sound2": open("/path/to/audio2.wav", "rb")
}

response = requests.post(url, headers=headers, files=files)

if response.status_code == 200:
    print(response.json())
else:
    print(f"Error: {response.json()}")
            

Notes