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
- nationalCode: A unique identifier for the user.
- userImageFile: An image file sent as part of form data.
Example Request
[HttpPost] public async TaskSaveImage([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: Defines an asynchronous method that returns anSaveImage([FromForm] string nationalCode, [FromForm] IFormFile userImageFile)
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
- video: A video file sent as part of form data. The video should capture the user performing designated actions.
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: Defines an asynchronous method that returns anLivenessDetection([FromForm] IFormFile video)
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.DeserializeObjectPurpose: Sends the HTTP request and processes the response.(responseString);
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
- image1: The first image file sent as part of form data.
- image2: The second image file sent as part of form data.
Example Request
[HttpPost] public async TaskVerification([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: Defines an asynchronous method that returns anVerification([FromForm] IFormFile image1, [FromForm] IFormFile image2)
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.DeserializeObjectPurpose: Sends the HTTP request and processes the response.(responseString);
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
- video: A video file sent as part of form data.
- nationalCode: A unique identifier for the user.
Example Request
[HttpPost] public async TaskUploadVideo([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: Defines an asynchronous method that returns anUploadVideo([FromForm] IFormFile video, [FromForm] string nationalCode)
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:
-
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.
- Liveness Detection: The API calls the LivenessDetection service to check if the video is live (not spoofed).
- 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
Authorization: Bearer <token>– A valid Bearer Token is required for authentication.
Input Parameters
- video: A video file (multipart/form-data) capturing the user's actions.
- image: An image file (multipart/form-data) of the user.
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:
0: The two faces are exactly the same.1: The two faces are almost identical.2: The two faces are moderately similar.3: The two faces are completely different.
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:
-
Receive Image: The API accepts an image file via multipart form data:
image: The image file (JPG, JPEG, or PNG) containing a face.
-
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). - 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
Authorization: Bearer <token>– A valid Bearer Token is required for authentication.
Input Parameters
- image: An image file (multipart/form-data) in JPG, JPEG, or PNG format containing a face.
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:
400 Bad Request: Returned when the input image is missing, has an unsupported format, or no face is detected.{ "error": "No image provided." }or{ "error": "No face detected in the image." }401 Unauthorized: Returned when the Bearer Token is missing or invalid.{ "error": "Unauthorized" }500 Internal Server Error: Returned when an error occurs during processing (e.g., failure in the external face detection service).{ "error": "Error processing image: <details>" }
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
- Only JPG, JPEG, and PNG image formats are supported for input.
- The output is always a PNG file, regardless of the input format.
- The API crops only the first detected face in the image.
- Ensure the Bearer Token is valid to avoid
401 Unauthorizederrors. - Contact
[email protected]to obtain a valid Bearer Token or for support.
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:
-
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.
- 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
Authorization: Bearer <token>– A valid Bearer Token is required for authentication.
Input Parameters
- sound1: The first audio file (multipart/form-data) containing the speaker's voice (e.g., WAV, MP3).
- sound2: The second audio file (multipart/form-data) containing the speaker's voice (e.g., WAV, MP3).
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:
0: The two voices are exactly the same.1: The two voices are highly similar.2: The two voices are moderately similar.3: The two voices are completely different.
Your application should determine the final verification outcome based on the similarityLevel value.
Example Request
[HttpPost] public async TaskSpeakerVerification([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: Defines an asynchronous method that returns anSpeakerVerification([FromForm] IFormFile sound1, [FromForm] IFormFile sound2)
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.DeserializeObjectPurpose: Sends the HTTP request and processes the response.(responseString);
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
- Supported audio formats include WAV and MP3.
- Ensure the audio files contain clear voice recordings for accurate verification.
- The API compares voice features to determine similarity.
- Ensure the Bearer Token is valid to avoid
401 Unauthorizederrors. - Contact
[email protected]to obtain a valid Bearer Token or for support.