Skip to main content

Overview

The HikCentral Synchronization module manages the bidirectional data flow between the SIAD academic system and the HikCentral biometric platform. This ensures that access control data remains current and accurate across both systems.

Architecture

System Components

SIAD Database

SQL Server storing personnel photos and academic data

Laravel Backend

API middleware handling authentication and data transformation

HikCentral Artemis API

Biometric system API with digital signature authentication

Data Flow

Authentication

Digital Signature

All requests to HikCentral require HMAC-SHA256 digital signature:
// Backend signature generation
$appKey = env('HIKCENTRAL_APP_KEY');
$appSecret = env('HIKCENTRAL_APP_SECRET');

$timestamp = time() * 1000;
$signature = base64_encode(hash_hmac('sha256', $data, $appSecret, true));

$headers = [
    'X-Ca-Key' => $appKey,
    'X-Ca-Signature' => $signature,
    'X-Ca-Signature-Headers' => 'x-ca-timestamp',
    'X-Ca-Timestamp' => $timestamp,
    'Content-Type' => 'application/json'
];
App Key and Secret must be kept secure in environment variables. Never expose these in frontend code.

Person Registration

Initial Registration Process

1

Fetch Person Data from SIAD

Retrieve complete profile including:
  • Full name (NombInfPer, ApellInfPer, ApellMatInfPer)
  • ID number (CIInfPer)
  • Email (mailInst)
  • Photo (binary data)
2

Convert Photo to Base64

Transform binary photo data to base64 string:
$photoBase64 = base64_encode($photoBinary);
3

Prepare Person Object

Create HikCentral person structure:
{
  "personName": "Juan Perez Garcia",
  "personId": "1234567890",
  "email": "[email protected]",
  "orgIndexCode": "org123",
  "faceList": [
    {
      "faceData": "base64_encoded_photo_data"
    }
  ]
}
4

Send POST Request

Submit to Artemis API endpoint with signature:
// Frontend trigger
const response = await API.post(
  `/biometrico/sync-hikdoc-est-id/${cedula}`,
  {},
  { params: { idper: periodoSeleccionado } }
);
5

Process Response

Handle HikCentral response codes:
  • Code 0: Success (store returned PersonId)
  • Code 128: Photo incompatible
  • Code 131: Person already exists

Student Registration Endpoints

Individual Registration:
POST /biometrico/sync-hikdoc-est-id/{cedula}
Params: { idper: academicPeriodId }

Response:
{
  "code": "0",
  "msg": "Success",
  "data": "uuid-of-created-person"
}
Bulk Registration:
// Get pending list
GET /biometrico/get-pending-sync-est
Params: { carrera_name: programId }

Response:
{
  "pendientes": [
    { "CIInfPer": "1234567890", "NombInfPer": "Juan", ... },
    { "CIInfPer": "0987654321", "NombInfPer": "Maria", ... }
  ]
}

Teacher/Staff Registration Endpoints

Individual Registration:
POST /biometrico/sync-hikcentral/{cedula}

Response:
{
  "code": "0",
  "msg": "Success",
  "data": "uuid-of-created-person"
}
Bulk Registration:
GET /biometrico/get-pending-sync
Params: { tipoFilter: personnelType }

Response:
{
  "pendientes": [
    { "CIInfPer": "1111111111", "TipoInfPer": "D", ... }
  ]
}

Person Updates

Update Existing Photo

When facial similarity is low, photos can be updated:
1

Retrieve PersonId

Get HikCentral UUID from verification:
const response = await API.get(`/biometrico/getperson-est/${cedula}`);
this.personIdHC = response.data.personId;
2

Send Update Request

Include PersonId in request body:
// Student update
await API.post(
  `/biometrico/sync-hikdoc-update/${cedula}`,
  { personaId: personIdHC },
  { params: { idper: periodoSeleccionado } }
);

// Teacher update
await API.post(
  `/biometrico/sync-hikdupdatedoce/${cedula}`,
  { personaId: personIdHC }
);
3

Verify Update

Re-fetch photo and calculate new similarity
Updates require the PersonId UUID from HikCentral. Always verify registration status before attempting updates.

Status Verification

Check Registration Status

Single Person Verification:
async verificarRegistroHC(ci) {
  this.cargandoStatus = true;
  try {
    // Students
    const response = await API.get(`/biometrico/getperson-est/${ci}`);
    
    // Teachers/Staff
    // const response = await API.get(`/biometrico/getperson/${ci}`);
    
    this.personIdHC = response.data.personId;
    this.estaRegistrado = response.data.registrado;
  } catch (error) {
    this.estaRegistrado = false;
  } finally {
    this.cargandoStatus = false;
  }
}
Response Structure:
{
  "registrado": true,
  "personId": "8a8a8a8a-1234-5678-9abc-def012345678"
}

Bulk Status Verification

Verify multiple people sequentially:
async verificarRegistrosMasivos() {
  const items = this.filteredpostulaciones;
  
  for (let post of items) {
    if (this.cargando) break; // Allow user to cancel
    
    try {
      const res = await API.get(`/biometrico/getperson-est/${post.CIInfPer}`);
      post.estaRegistradoHC = res.data.registrado;
    } catch (e) {
      post.estaRegistradoHC = false;
    }
    
    // Rate limiting: 50ms delay between requests
    await new Promise(resolve => setTimeout(resolve, 50));
  }
}
Rate limiting is essential to prevent HTTP 429 (Too Many Requests) errors from HikCentral API.

Bulk Synchronization

Mass Sync Workflow

1

Initiate Sync

User clicks “Sincronizar Pendientes (Masivo)” button
async iniciarSincronizacionMasiva() {
  if (!confirm("Se buscarán usuarios no registrados y se enviarán a HikCentral. ¿Continuar?")) {
    return;
  }
  this.syncMode = true;
  this.syncIndex = 0;
}
2

Fetch Pending List

Retrieve all unregistered persons:
const { data } = await API.get('/biometrico/get-pending-sync-est', {
  params: { carrera_name: this.selectedCarrera }
});
this.pendientes = data.pendientes;
3

Sequential Processing

Process each person one by one:
for (const p of this.pendientes) {
  this.currentSyncName = p.NombInfPer;
  
  try {
    const res = await API.post(`/biometrico/sync-hikdoc/${p.CIInfPer}`);
    
    if (res.data.code === "0" || res.data.msg === "Success") {
      console.log(`✅ Sincronizado: ${p.CIInfPer}`);
    } else if (res.data.code === "131") {
      console.warn(`⚠️ Ya registrado: ${p.CIInfPer}`);
    } else if (res.data.code === "128") {
      console.warn(`❌ Foto incompatible: ${p.CIInfPer}`);
    }
  } catch (e) {
    console.error(`❌ Error: ${p.CIInfPer}`, e.response?.data);
  }
  
  this.syncIndex++;
  await new Promise(resolve => setTimeout(resolve, 300));
}
4

Complete and Refresh

Show completion message and refresh table:
alert("Sincronización masiva finalizada.");
this.getAdministrativosD(this.currentPage, this.searchQuery, this.selectedCarrera);
this.syncMode = false;

Progress Tracking

Real-time progress display during sync:
<div v-if="syncMode" class="mb-4 p-4 border rounded-xl bg-gray-50">
  <div class="flex justify-between mb-2">
    <span class="text-sm font-medium">
      Sincronizando con HikCentral: {{ syncIndex }} / {{ pendientes.length }}
    </span>
    <span class="text-sm font-bold">{{ progressSync }}%</span>
  </div>
  
  <div class="w-full bg-gray-200 rounded-full h-2.5">
    <div class="bg-brand-500 h-2.5 rounded-full transition-all duration-300"
         :style="{ width: progressSync + '%' }">
    </div>
  </div>
  
  <p class="text-xs mt-2 text-gray-500 italic">
    Procesando: {{ currentSyncName }}
  </p>
</div>

<button 
  @click="iniciarSincronizacionMasiva" 
  :disabled="cargando || syncMode"
  class="btn btn-primary">
  Sincronizar Pendientes (Masivo)
</button>
Progress Calculation:
computed: {
  progressSync() {
    return this.pendientes.length > 0
      ? Math.round((this.syncIndex / this.pendientes.length) * 100)
      : 0;
  }
}

Photo Retrieval from HikCentral

Fetch Person Photo

Retrieve current photo from biometric system:
// Frontend photo URL
getPhotoUrl2(ci) {
  const baseURL = API.defaults.baseURL;
  return `${baseURL}/biometrico/gethick/${ci}?t=${new Date().getTime()}`;
}
Backend Process:
  1. Search person by ID in HikCentral
  2. Extract PersonId from search results
  3. Call face photo endpoint with PersonId
  4. Return image with proper content-type headers

Search Person Endpoint

// Backend search in HikCentral
POST /artemis/api/resource/v1/person/advance/personList
{
  "pageNo": 1,
  "pageSize": 1,
  "personId": "1234567890"
}

Response:
{
  "code": "0",
  "data": {
    "list": [
      {
        "personId": "uuid",
        "personName": "Juan Perez"
      }
    ]
  }
}

Response Codes

HikCentral API Codes

Code 0

Success - Operation completed successfully

Code 128

Invalid Photo - Photo format or quality incompatible

Code 131

Already Exists - Person already registered in system

Code 429

Too Many Requests - Rate limit exceeded

Error Handling

try {
  const response = await API.post(`/biometrico/sync-hikcentral/${cedula}`);
  
  switch (response.data.code) {
    case "0":
      alert(`✅ Registrado con éxito. ID: ${response.data.data}`);
      break;
    
    case "128":
      alert("❌ La foto no es compatible con HikCentral.");
      this.estaRegistrado = false;
      break;
    
    case "131":
      alert("⚠️ Usuario ya registrado en HikCentral.");
      this.estaRegistrado = true;
      break;
    
    default:
      alert(`⚠️ Respuesta del servidor: ${response.data.msg}`);
  }
} catch (error) {
  console.error("Error al sincronizar:", error);
  const mensaje = error.response?.data?.details?.msg || 
                  "Error desconocido al conectar con el Biométrico";
  alert(`❌ Error: ${mensaje}`);
}

Rate Limiting Strategy

Preventing API Overload

HikCentral API has strict rate limits. Sequential processing with delays is essential.
Implementation:
// Individual verification with 50ms delay
for (let post of items) {
  await API.get(`/biometrico/getperson/${post.CIInfPer}`);
  await new Promise(resolve => setTimeout(resolve, 50));
}

// Bulk registration with 300ms delay
for (const person of pendientes) {
  await API.post(`/biometrico/sync-hikdoc/${person.CIInfPer}`);
  await new Promise(resolve => setTimeout(resolve, 300));
}

Handling 429 Errors

catch (error) {
  if (error.response?.status === 429) {
    alert("El servidor reportó 'Too Many Requests' (429). " +
          "Por favor, inténtelo de nuevo en un momento.");
  }
}

Data Mapping

SIAD to HikCentral Field Mapping

SIAD FieldHikCentral FieldNotes
CIInfPerpersonIdID number
NombInfPer + ApellInfPer + ApellMatInfPerpersonNameFull name
mailInstemailInstitutional email
Fotografia (binary)faceData (base64)Photo conversion required
N/AorgIndexCodeFixed organization code

Person Object Structure

{
  "personName": "Juan Alberto Perez Garcia",
  "personId": "1234567890",
  "email": "[email protected]",
  "orgIndexCode": "organizationCode123",
  "faceList": [
    {
      "faceData": "/9j/4AAQSkZJRgABAQEAYABgAAD..."
    }
  ]
}

Best Practices

Synchronization Guidelines

  1. Always verify status before attempting registration
  2. Use PersonId for updates - never create duplicates
  3. Implement rate limiting in all bulk operations
  4. Handle response codes appropriately for each scenario
  5. Log failures during bulk sync for manual review
  6. Compare photos after synchronization to verify quality
  7. Refresh data after bulk operations complete
  8. Monitor progress during long-running batch processes
  9. Secure API credentials in environment variables
  10. Test with small batches before full sync

Troubleshooting

Common Issues

Cause: Photo doesn’t meet HikCentral quality requirementsSolution:
  • Verify photo resolution and format
  • Ensure face is clearly visible and frontal
  • Check for plain background
  • Consider re-taking photo in SIAD
Cause: Person already exists in HikCentralSolution:
  • Use update endpoint instead of create
  • Retrieve PersonId first
  • Check verification status before registration
Cause: Too many requests in short timeframeSolution:
  • Increase delay between requests (300ms+)
  • Process in smaller batches
  • Retry after waiting period
Cause: Browser caching old imagesSolution:
  • Add timestamp parameter to photo URLs
  • Update refreshKey after sync
  • Force browser cache clear

Build docs developers (and LLMs) love