Biometric Attendance System API

Complete Integration Documentation for School Management Systems

SIM800L Compatible
Version 2.2
Last Updated December 2024
Protocol HTTP POST only
Content Type application/json
Max Records 5000+ (Batched)

⚠️ Critical System Requirements

HTTP ONLY - NO HTTPS SUPPORT

The biometric device uses SIM800L GSM module which only supports HTTP protocol. HTTPS will not work.

Base API Endpoint

POST http://your-domain.com/attendance/post_attendance

Note: All attendance data is sent to this single endpoint via HTTP POST with JSON payload.

📊 Attendance Data Format

1. Check-In (Sign In) Example

{
"sID": "2",
"name": "Philip Nganda",
"tIn": 1766383045,
"Adm": "ADM001",
"lt": 1,
"cl": "Staff",
"mcn": "a",
"apikey": "school123",
"school": "Your School Name"
}

2. Check-Out (Sign Out) Example

{
"sID": "2",
"name": "Philip Nganda",
"tOut": 1766397445,
"Adm": "ADM001",
"lt": 4,
"cl": "Staff",
"mcn": "a",
"apikey": "school123",
"school": "Your School Name"
}

3. Late Status (lt) Field Explained

lt = 1
Late Comer
lt = 2
Early Leaver
lt = 3
Arrived on Time
lt = 4
Left on Time
lt = 5
No Time Limit Applied

4. Complete Field Reference

Field Type Required Description Example
sID String Required Unique student/staff ID from biometric device "101"
name String Required Full name as stored in device "John Doe"
tIn Integer Conditional Check-in time (Unix timestamp seconds) 1766383045
tOut Integer Conditional Check-out time (Unix timestamp seconds) 1766397445
Adm String Required Admission/Employee number (use "_" for none) "STU101"
lt Integer Required Late/Early status (1-5 as explained above) 3
cl String Required Class/Category/Department "Form 1A", "Staff", "Grade 5"
mcn String Optional Machine identifier (default "a") "gate1"
apikey String Required Your API key file name "school123"
school String Required School/Institution name "Your School Name"
Field
sID
Type
String
Required
Required
Description
Unique student/staff ID from biometric device
Example
"101"
Field
name
Type
String
Required
Required
Description
Full name as stored in device
Example
"John Doe"
Field
tIn
Type
Integer
Required
Conditional
Description
Check-in time (Unix timestamp seconds)
Example
1766383045
Field
tOut
Type
Integer
Required
Conditional
Description
Check-out time (Unix timestamp seconds)
Example
1766397445
Field
Adm
Type
String
Required
Required
Description
Admission/Employee number (use "_" for none)
Example
"STU101"
Field
lt
Type
Integer
Required
Required
Description
Late/Early status (1-5 as explained above)
Example
3
Field
cl
Type
String
Required
Required
Description
Class/Category/Department
Example
"Form 1A", "Staff", "Grade 5"
Field
mcn
Type
String
Required
Optional
Description
Machine identifier (default "a")
Example
"gate1"
Field
apikey
Type
String
Required
Required
Description
Your API key file name
Example
"school123"
Field
school
Type
String
Required
Required
Description
School/Institution name
Example
"Your School Name"
📝 Note: Either tIn or tOut must be present to indicate check-in or check-out.

🔄 Data Synchronization Operations

Download Student List to Device

1
Initial Download Request

Device requests student list:

{
"dwnld": "1",
"mcn": "a",
"apikey": "school123",
"school": "Your School Name"
}
2
Server Response Format

Returns students in CSV-like format with * separators:

ID,FullName,Phone,AdmissionNo,ParentPhone,ExpectedIn,ExpectedOut*

Example Response:

1,John Doe,0727xxxxxx,STU101,0727xxxxxx,07:30,16:00*
2,Jane Smith,0727xxxxxx,STU102,optional,07:30,16:00*
101,Peter Johnson,0723222222,STU103,,07:30,16:00*^
3
Response Markers
  • * - Record separator
  • ^ - End of download (complete)
  • } - Continue marker (more data available)
  • ID_* - Suspended/Inactive student

📊 Batch Processing Details

  • Batch Size: 100 records per query
  • Chunk Size: 20KB per transmission
  • Memory Usage: ~7KB per batch
  • Timeout: 5 minutes maximum
  • Memory Limit: 256MB allocated
  • Output Flushing: Every 50 records
📝 NamesList CSV Format Structure:

The nameslist uses a comma-separated CSV format with class/group headers embedded within the list:

1,staff 2025,,,,,*
2,Philip N,0727506940,4553,448,7:00,17:00*
3,Jason Mumo N,0727506940,4205,555,,*
4,_,,,,,*
5,Ruth Mueni,0727506940,22,,8:00,18:00*
6,Rodgers Kyanzu,0791226117,4567,0727506940,7:00,18:00*
7,Grace N,0727506940,333,,8:30,17:00*
8,Marlon Macharia,0727506940,11,0727506940,8:00,18:00*
9,,,,,,*
10,,,,,,*
11,,,,,,*
12,,,,,,*
13,,,,,,*
14,,,,,,*
15,,,,,,*
16,,,,,,*
17,,,,,,*
18,,,,,,*
19,,,,,,*
20,Form 1s,,,,,*
21,Suleiman Ogollah Otieno,,,,,*
22,Evans Ogola,,,,,*
23,Jane Mboya,,,,,*
24,Moses Odhiambo Otieno,0727506940,1195,103074847,6:00,17:00*
25,Nancy Kavai,0727506940,1525,103074847,7:00,18:00*
26,Vincent Onyango,0727506940,184,103074847,6:00,17:00*
27,Lilian Simiyu,0727506940,1546,103074847,6:00,17:00*
28,Zainab Mohamed,0727506940,134,103074847,6:00,17:00*^
Format Specifications:
Column Content Example
1 ID Number 1, 2, 20, 21
2 Name/Category "staff 2025", "Form 1s", "Philie N"
3 Phone Number 0727506940 replace with "'0000" if not needed
4 Admission No 4205
5 Parent Phone 2 optional
6 Expected In 7:00 (optional)
7 Expected Out 17:00
End Record Separator * (required for each line)
📋 Format Analysis (What you see)
  • Separator: Comma between fields
  • Phone Format: 0XXXXXXXXX (10 digits)
  • Grouping: Category rows at IDs 1, 20
  • Empty Data: Consecutive commas
  • Status: _ = inactive
  • Placeholders: IDs 9-19 reserved
  • Time: 24-hour (HH:MM)
  • End Marker: * per line
  • Hierarchy: Records follow categories
⚙️ Implementation Rules (What to do)
  • Validation: 10-digit phone, may start with 0
  • Categories: Minimal data (ID + name only)
  • Deletions: Name = _ to remove (or empty name slot)
  • Structure: Maintain 7 fields per line
  • Planning: Reserve ID blocks for growth
  • Time Rules: HH:MM, 00-23 hours
  • Required: * terminator on every line
  • Characters: ASCII names, no symbols
💡 Sample PHP Code for CSV Generation:
// Generate comma-separated CSV
foreach ($students as $student) {
$line = $student['id'] . ',' . 
$student['name'] . ',' . 
$student['phone'] . ',' . 
$student['admission_no'] . ',' . 
$student['parent_phone'] . ',' . 
$student['expected_in'] . ',' . 
$student['expected_out'] . '*';

echo $line;
}

Handle Student Deletions

When students are deleted from your system, notify the device:

{
"dlt": "1-5,7,9-11",
"mcn": "a",
"apikey": "school123",
"school": "Your School Name"
}

Deletion Format Explained

Format Meaning Example IDs
"1-5" Delete IDs 1 through 5 1, 2, 3, 4, 5
"7" Delete single ID 7 7
"9-11" Delete IDs 9 through 11 9, 10, 11
"1-5,7,9-11" Combination of ranges 1,2,3,4,5,7,9,10,11
💡 Implementation Tip:

After processing deletions, the device will automatically request the updated student list. Deleted students are marked with underscores in their fields (soft delete).

Example Implementation (PHP)

// Parse deletion ranges
function parseDeletionRanges($rangeString) {
$deletedIds = [];
$ranges = explode(',', $rangeString);

foreach ($ranges as $range) {
if (strpos($range, '-') !== false) {
list($start, $end) = explode('-', $range);
$deletedIds = array_merge($deletedIds, range($start, $end));
} else {
$deletedIds[] = (int)$range;
}
}

return array_unique($deletedIds);
}

// Process deletions
$deletedIds = parseDeletionRanges($data['dlt']);

foreach ($deletedIds as $id) {
// Soft delete in your database
$stmt = $pdo->prepare("
UPDATE students 
SET status = 'deleted', 
deleted_at = NOW() 
WHERE student_id = ?
");
$stmt->execute([$id]);
}

General System Queries

1. Get Current Date & Time

{
"gen": "2",
"apikey": "school123",
"school": "Your School Name"
}

Response Format: dd~mm~yyyy~hh~mm~ss

Example: 22~12~2024~14~30~45

2. System Identification

{
"gen": "3",
"apikey": "school123",
"school": "Your School Name"
}

Response: kauwi:ins

Download Exam Results (Work in progress)

{
"dwnld": "2",
"mcn": "a",
"apikey": "school123",
"school": "Your School Name"
}

Response Format

StudentName,StudentPhone,ParentPhone,Eng:A,Kis:B,Maths:C,...*
📚 Subject Codes:
  • Eng - English
  • Kis - Kiswahili
  • Maths - Mathematics
  • Bio - Biology
  • Phy - Physics
  • Chem - Chemistry
  • MEAN - Average Grade

Download Bell Timetable

{
"dwnld": "3",
"mcn": "a",
"apikey": "school123",
"school": "Your School Name"
}

Response Format

Day,Time1,Time2,...,Time22*

Example: Mon,08:00,09:00,10:00,...,16:00*

⏰ Features:
  • 7 days (Monday to Sunday)
  • 22 time slots per day (A-V columns) (can be more or less)
  • Includes bell duration at the end (after Sunday row (eg 3 (for 3 seconds)))

🛠️ Implementation Examples

Complete PHP Implementation

<?php
// Configure API
define('API_ENDPOINT', 'http://your-domain.com/attendance/post_attendance');
define('API_KEY', 'school123');
define('SCHOOL_NAME', 'Your School Name');

class BiometricIntegration {

/**
* Process attendance data from biometric device
*/
public function processAttendance($data) {
// Validate API key
if (!isset($data['apikey']) || $data['apikey'] !== API_KEY) {
return ['error' => 'Invalid API key'];
}

// Extract data
$studentId = $data['sID'] ?? '';
$name = $data['name'] ?? '';
$admission = ($data['Adm'] === '_') ? null : $data['Adm'];
$category = $data['cl'] ?? '';
$lateStatus = (int)($data['lt'] ?? 5);
$machineId = $data['mcn'] ?? 'a';

// Determine operation
$isCheckIn = isset($data['tIn']);
$timestamp = $isCheckIn ? (int)$data['tIn'] : (int)$data['tOut'];

// Convert timestamp
$dateTime = date('Y-m-d H:i:s', $timestamp);
$date = date('Y-m-d', $timestamp);

// Process based on operation type
if ($isCheckIn) {
return $this->handleCheckIn($studentId, $name, $admission, 
$category, $lateStatus, $machineId, 
$dateTime, $date);
} else {
return $this->handleCheckOut($studentId, $dateTime, $date);
}
}

/**
* Handle check-in operation
*/
private function handleCheckIn($studentId, $name, $admission, $category, 
$lateStatus, $machineId, $dateTime, $date) {
// Prevent duplicate check-ins within 1 minute
$stmt = $this->pdo->prepare("
SELECT id FROM attendance 
WHERE student_id = ? 
AND attendance_date = ?
AND check_in_time >= DATE_SUB(?, INTERVAL 1 MINUTE)
");
$stmt->execute([$studentId, $date, $dateTime]);

if ($stmt->rowCount() > 0) {
return ['success' => true, 'message' => 'Duplicate prevented'];
}

// Insert new check-in
$stmt = $this->pdo->prepare("
INSERT INTO attendance 
(student_id, student_name, admission_no, class_category, 
check_in_time, attendance_date, late_status, machine_id, status)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'present')
");

$stmt->execute([
$studentId, $name, $admission, $category,
$dateTime, $date, $lateStatus, $machineId
]);

return ['success' => true, 'id' => $this->pdo->lastInsertId()];
}

/**
* Handle check-out operation
*/
private function handleCheckOut($studentId, $dateTime, $date) {
$stmt = $this->pdo->prepare("
UPDATE attendance 
SET check_out_time = ?, status = 'completed'
WHERE student_id = ? 
AND attendance_date = ?
AND check_out_time IS NULL
ORDER BY id DESC
LIMIT 1
");

$stmt->execute([$dateTime, $studentId, $date]);

return ['success' => true, 'updated' => $stmt->rowCount()];
}

/**
* Handle student list download
*/
public function handleDownload($data) {
// Get all active students
$stmt = $this->pdo->prepare("
SELECT 
id, 
CONCAT(first_name, ' ', COALESCE(middle_name, ''), ' ', last_name) as name,
phone,
admission_no,
parent_phone,
expected_in_time,
expected_out_time,
status
FROM students
WHERE status = 'active'
ORDER BY id ASC
");
$stmt->execute();
$students = $stmt->fetchAll(PDO::FETCH_ASSOC);

// Format response
$response = '';
$counter = 0;
$outputLength = 0;

foreach ($students as $student) {
$counter++;

if ($student['status'] !== 'active') {
$response .= $student['id'] . '_*';
continue;
}

$row = sprintf('%s,%s,%s,%s,%s,%s,%s*',
$student['id'],
$student['name'],
$student['phone'] ?? '',
$student['admission_no'] ?? '',
$student['parent_phone'] ?? '',
$student['expected_in_time'] ?? '07:30',
$student['expected_out_time'] ?? '16:00'
);

$response .= $row;
$outputLength += strlen($row);

// Batch processing: Send in chunks
if ($outputLength > 20000) { // 20KB
$response .= '}';
break;
}
}

if ($outputLength <= 20000) {
$response .= '^';
}

return $response;
}

/**
* Handle student deletions
*/
public function handleDeletion($rangeString) {
$deletedIds = $this->parseRange($rangeString);

foreach ($deletedIds as $id) {
// Soft delete
$stmt = $this->pdo->prepare("
UPDATE students 
SET status = 'deleted', 
deleted_at = NOW()
WHERE id = ?
");
$stmt->execute([$id]);
}

return count($deletedIds) . ' students deleted';
}

/**
* Parse range string (e.g., "1-5,7,9-11")
*/
private function parseRange($rangeString) {
$ids = [];
$ranges = explode(',', $rangeString);

foreach ($ranges as $range) {
if (strpos($range, '-') !== false) {
list($start, $end) = explode('-', $range);
$ids = array_merge($ids, range($start, $end));
} else {
$ids[] = (int)$range;
}
}

return array_unique($ids);
}
}

// Usage example
$integration = new BiometricIntegration();

// Get JSON input
$json = file_get_contents('php://input');
$data = json_decode($json, true);

// Route the request
if (isset($data['sID']) || isset($data['tIn']) || isset($data['tOut'])) {
$result = $integration->processAttendance($data);
} elseif (isset($data['dwnld'])) {
$result = $integration->handleDownload($data);
} elseif (isset($data['dlt'])) {
$result = $integration->handleDeletion($data['dlt']);
} else {
$result = ['error' => 'Invalid request'];
}

// Send response (device expects simple responses)
echo is_string($result) ? $result : '1';
?>

Node.js Implementation

const express = require('express');
const mysql = require('mysql2/promise');
const app = express();

app.use(express.json());

// Database configuration
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'password',
database: 'school',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});

// Main endpoint
app.post('/api/attendance', async (req, res) => {
try {
const data = req.body;

// Validate API key
if (data.apikey !== process.env.API_KEY) {
return res.status(401).send('Unauthorized');
}

// Route based on request type
if (data.sID || data.tIn || data.tOut) {
await handleAttendance(data);
res.send('1');
} else if (data.dwnld) {
const result = await handleDownload(data);
res.send(result);
} else if (data.dlt) {
await handleDeletion(data.dlt);
res.send('1');
} else if (data.gen === '2') {
// Return current datetime
const now = new Date();
const response = `${now.getDate()}~${now.getMonth()+1}~${now.getFullYear()}~${now.getHours()}~${now.getMinutes()}~${now.getSeconds()}`;
res.send(response);
} else if (data.gen === '3') {
res.send('kauwi:ins');
} else {
res.status(400).send('Invalid request');
}
} catch (error) {
console.error('Error:', error);
res.status(500).send('1'); // Still respond to prevent device timeout
}
});

async function handleAttendance(data) {
const connection = await pool.getConnection();

try {
const studentId = data.sID;
const name = data.name || '';
const admission = data.Adm === '_' ? null : data.Adm;
const category = data.cl || '';
const lateStatus = parseInt(data.lt) || 5;
const machineId = data.mcn || 'a';

// Determine operation
const isCheckIn = 'tIn' in data;
const timestamp = isCheckIn ? parseInt(data.tIn) : parseInt(data.tOut);

// Convert timestamp
const dateTime = new Date(timestamp * 1000);
const dateStr = dateTime.toISOString().split('T')[0];
const timeStr = dateTime.toISOString().split('T')[1].split('.')[0];

if (isCheckIn) {
// Check for duplicate
const [existing] = await connection.execute(`
SELECT id FROM attendance 
WHERE student_id = ? 
AND DATE(check_in_time) = ?
AND TIMESTAMPDIFF(MINUTE, check_in_time, ?) BETWEEN -1 AND 1
`, [studentId, dateStr, dateTime]);

if (existing.length > 0) {
return; // Duplicate, don't insert
}

// Insert check-in
await connection.execute(`
INSERT INTO attendance 
(student_id, student_name, admission_no, class_category,
check_in_time, attendance_date, late_status, machine_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`, [studentId, name, admission, category, 
dateTime, dateStr, lateStatus, machineId]);

} else {
// Update check-out
await connection.execute(`
UPDATE attendance 
SET check_out_time = ?
WHERE student_id = ? 
AND DATE(attendance_date) = ?
AND check_out_time IS NULL
ORDER BY id DESC
LIMIT 1
`, [dateTime, studentId, dateStr]);
}
} finally {
connection.release();
}
}

async function handleDownload(data) {
const connection = await pool.getConnection();

try {
const [students] = await connection.execute(`
SELECT 
id,
CONCAT(first_name, ' ', COALESCE(middle_name, ''), ' ', last_name) as name,
phone,
admission_no,
parent_phone,
expected_in_time,
expected_out_time,
status
FROM students
WHERE status = 'active'
ORDER BY id ASC
`);

let response = '';
let outputLength = 0;

for (const student of students) {
if (student.status !== 'active') {
response += `${student.id}_*`;
continue;
}

const row = `${student.id},${student.name},${student.phone || ''},${student.admission_no || ''},${student.parent_phone || ''},${student.expected_in_time || '07:30'},${student.expected_out_time || '16:00'}*`;
response += row;
outputLength += row.length;

if (outputLength > 20000) {
response += '}';
break;
}
}

if (outputLength <= 20000) {
response += '^';
}

return response;
} finally {
connection.release();
}
}

async function handleDeletion(rangeString) {
const ids = parseRange(rangeString);
const connection = await pool.getConnection();

try {
await connection.execute(`
UPDATE students 
SET status = 'deleted', 
deleted_at = NOW()
WHERE id IN (?)
`, [ids]);
} finally {
connection.release();
}
}

function parseRange(rangeString) {
const ids = new Set();
const ranges = rangeString.split(',');

for (const range of ranges) {
if (range.includes('-')) {
const [start, end] = range.split('-').map(Number);
for (let i = start; i <= end; i++) {
ids.add(i);
}
} else {
ids.add(Number(range));
}
}

return Array.from(ids);
}

app.listen(3000, () => {
console.log('Server running on port 3000');
});

Python (Flask) Implementation

from flask import Flask, request, jsonify
import mysql.connector
from datetime import datetime
import json
import os

app = Flask(__name__)

# Configuration
API_KEY = os.getenv('BIOMETRIC_API_KEY', 'school123')

def get_db_connection():
return mysql.connector.connect(
host='localhost',
user='root',
password='password',
database='school'
)

@app.route('/attendance/post_attendance', methods=['POST'])
def handle_attendance():
try:
data = request.get_json()

# Validate API key
if data.get('apikey') != API_KEY:
return 'Unauthorized', 401

# Route based on request type
if 'sID' in data or 'tIn' in data or 'tOut' in data:
handle_attendance_data(data)
return '1'
elif 'dwnld' in data:
result = handle_download(data)
return result
elif 'dlt' in data:
handle_deletion(data['dlt'])
return '1'
elif data.get('gen') == '2':
# Return current datetime
now = datetime.now()
response = f"{now.day}~{now.month}~{now.year}~{now.hour}~{now.minute}~{now.second}"
return response
elif data.get('gen') == '3':
return 'kauwi:ins'
else:
return 'Invalid request', 400

except Exception as e:
print(f"Error: {e}")
return '1'  # Always respond to prevent device timeout

def handle_attendance_data(data):
conn = get_db_connection()
cursor = conn.cursor()

try:
student_id = data.get('sID')
name = data.get('name', '')
admission = None if data.get('Adm') == '_' else data.get('Adm')
category = data.get('cl', '')
late_status = int(data.get('lt', 5))
machine_id = data.get('mcn', 'a')

# Determine operation
is_check_in = 'tIn' in data
timestamp = int(data.get('tIn' if is_check_in else 'tOut'))

# Convert timestamp
dt = datetime.fromtimestamp(timestamp)
date_str = dt.strftime('%Y-%m-%d')
time_str = dt.strftime('%Y-%m-%d %H:%M:%S')

if is_check_in:
# Check for duplicate
cursor.execute("""
SELECT id FROM attendance 
WHERE student_id = %s 
AND attendance_date = %s
AND TIMESTAMPDIFF(MINUTE, check_in_time, %s) BETWEEN -1 AND 1
""", (student_id, date_str, time_str))

if cursor.fetchone():
return  # Duplicate found

# Insert check-in
cursor.execute("""
INSERT INTO attendance 
(student_id, student_name, admission_no, class_category,
check_in_time, attendance_date, late_status, machine_id)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
""", (student_id, name, admission, category, 
time_str, date_str, late_status, machine_id))

else:
# Update check-out
cursor.execute("""
UPDATE attendance 
SET check_out_time = %s
WHERE student_id = %s 
AND attendance_date = %s
AND check_out_time IS NULL
ORDER BY id DESC
LIMIT 1
""", (time_str, student_id, date_str))

conn.commit()

finally:
cursor.close()
conn.close()

def handle_download(data):
conn = get_db_connection()
cursor = conn.cursor(dictionary=True)

try:
cursor.execute("""
SELECT 
id,
CONCAT(first_name, ' ', COALESCE(middle_name, ''), ' ', last_name) as name,
phone,
admission_no,
parent_phone,
expected_in_time,
expected_out_time,
status
FROM students
WHERE status = 'active'
ORDER BY id ASC
""")

response = ''
output_length = 0

for student in cursor.fetchall():
if student['status'] != 'active':
response += f"{student['id']}_*"
continue

row = f"{student['id']},{student['name']},{student['phone'] or ''},{student['admission_no'] or ''},{student['parent_phone'] or ''},{student['expected_in_time'] or '07:30'},{student['expected_out_time'] or '16:00'}*"
response += row
output_length += len(row)

if output_length > 20000:
response += '}'
break

if output_length <= 20000:
response += '^'

return response

finally:
cursor.close()
conn.close()

def handle_deletion(range_string):
ids = parse_range(range_string)
conn = get_db_connection()
cursor = conn.cursor()

try:
placeholders = ','.join(['%s'] * len(ids))
cursor.execute(f"""
UPDATE students 
SET status = 'deleted', 
deleted_at = NOW()
WHERE id IN ({placeholders})
""", ids)
conn.commit()

finally:
cursor.close()
conn.close()

def parse_range(range_string):
ids = set()
ranges = range_string.split(',')

for r in ranges:
if '-' in r:
start, end = map(int, r.split('-'))
ids.update(range(start, end + 1))
else:
ids.add(int(r))

return list(ids)

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)

✅ Implementation Checklist

🚨 Troubleshooting Guide

Issue Symptoms Solution
No Data Received Empty POST body, no errors in logs Check php://input or request body parsing
Device Timeout Device retries same request Optimize queries, add database indexes
Wrong Timestamps Incorrect dates/times stored Verify Unix timestamp (seconds, not milliseconds)
Partial Downloads Only some students downloaded Check chunk size and continuation logic
Deletion Issues Students not marked as deleted Verify range parsing logic
Duplicate Records Same student checked in multiple times Implement 1-minute duplicate check window
⚠️ Emergency Contact:

If the biometric device stops sending data:

  1. Check device network connectivity (SIM card signal)
  2. Verify HTTP endpoint is accessible (not HTTPS)
  3. Check server error logs for recent issues
  4. Test API endpoint with cURL or Postman
  5. Contact technical support with error details
👁️ Live Stats
0
0 Today
0 Unique
🌍 Loading...