For a cloud-based multi-tenant HR/payroll system receiving real-time attendance events from ZKTeco devices, think of it as a distributed system rather than an attendance feature. The difficult part is not receiving punches—it's guaranteeing that every punch is captured exactly once, correctly mapped to the right employee, office, timezone, shift, and payroll period.
Employee
↓
ZKTeco Device
↓
Internet
↓
Cloud Push Endpoint
↓
Raw Attendance Event Table
↓
Queue Processing
↓
Attendance Engine
↓
Attendance Records
↓
Payroll / HR
Never write directly into attendance tables from the device callback.
Always:
- Receive event
- Store raw event immediately
- Return success
- Process asynchronously
This protects against outages and bugs.
Since your SaaS is multi-tenant:
Every device must belong to:
Tenant
└── Branch/Office
└── Device
Store:
tenant_id
branch_id
device_id
serial_number
Never trust device serial alone.
Example:
ABC Garments
├─ Dhaka Office
│ ├─ Device A
│ └─ Device B
└─ Chittagong Office
├─ Device C
Attendance must know:
which tenant
which office
which device
When customer adds a device:
Store:
Device Serial Number
Device Model
Firmware Version
Push URL
Office
Timezone
Status
Verify:
- Device exists
- Device not already assigned to another tenant
- Device active
Prevent:
Same device attached to two tenants
This is the biggest real-world issue.
Scenario:
9:00 AM Employee punches
Internet down
Device should:
Store locally
Later:
Internet restored
Device pushes backlog
Your system must support:
Event may arrive:
Punch Time: 9:00 AM
Arrival Time: 11:45 AM
Never use server receive time as attendance time.
Use:
event_timestamp_from_device
Store both:
device_time
server_received_at
Extremely common.
Reasons:
- Network retry
- Device retry
- Timeout
- Cloud callback failure
You may receive:
same punch 2 times
same punch 5 times
same punch 20 times
Need deduplication.
Store unique key:
device_serial
employee_identifier
timestamp
verification_method
Or if device sends:
log_id
record_id
transaction_id
Use that.
Possible:
9:05 arrives first
9:00 arrives later
System must reorder.
Always sort by:
actual punch time
not:
received time
Very common.
Device clock may be:
5 min fast
10 min slow
1 day wrong
Need:
Store:
device_time
server_time
difference
Alert if:
difference > 5 minutes
Critical for payroll.
Store:
tenant timezone
office timezone
Never assume UTC+6.
Example:
Client may have:
- Bangladesh
- UAE
- Malaysia
Always save:
UTC timestamp
original timezone
ZKTeco may send:
PIN
User ID
Card Number
Employee Number
Need mapping table:
device_user_id
employee_id
tenant_id
Because:
Device User 1001
means nothing globally.
Store method used:
Face
Fingerprint
RFID
Password/PIN
Palm
Example:
verification_method
Useful for audits.
Huge issue.
Employee created in HR software.
Need to decide:
Software pushes user to device.
Admin manually creates user on device.
Option A is strongly recommended.
Maintain:
Employee
↔ Device User
sync status.
Example:
Employee moved from Dhaka to Chittagong
Need:
- Keep old attendance
- Sync new branch device access
Never hard delete.
If employee leaves:
inactive
Attendance history remains.
Attendance itself is easy.
Shift processing is hard.
Examples:
9 AM - 6 PM
10 PM - 6 AM
Weekly change
9-1
2-6
Raw logs should remain untouched.
Attendance engine should calculate shifts separately.
Example:
IN
OUT
Expected.
Actual:
IN only
Need rules:
Missing Out
Missing In
Manual correction
Employee punches:
9:00
9:02
9:05
Need policy:
Within:
30 sec
60 sec
2 min
Configurable.
Some ZKTeco devices:
- Limited logs
- Limited users
- Limited fingerprints
Monitor:
storage usage
Alert customer.
Need heartbeat monitoring.
Track:
last_seen_at
Statuses:
Online
Offline
Inactive
Alert:
No communication for X hours
Never expose a public endpoint without verification.
Validate:
Protect against fake attendance submissions.
Someone replays old requests.
Store:
event unique id
Reject duplicates.
Every attendance modification should be tracked.
Example:
Original punch
Modified by HR
Modified time
Reason
Payroll disputes happen.
Audit logs are essential.
Never delete raw events.
Keep table:
attendance_device_logs
Immutable.
If attendance engine changes later, you can rebuild attendance.
Cloud callback received.
Queue processing fails.
Need:
pending
processing
completed
failed
with retries.
Your endpoint must be idempotent.
If device sends same request 10 times:
Result:
1 attendance event
not 10.
Example:
Payroll finalized for March.
Late log arrives later.
Need policy:
Allow recalculation
or
Create exception
Never silently modify closed payroll.
Need support for:
Remote workers
Field staff
Device failure
Mark source:
DEVICE
MANUAL
MOBILE
API
Store:
source_device_id
source_type
verification_method
for every punch.
Old device dies.
New device installed.
Need:
retain history
relink office
sync users
without data loss.
Device moved between offices.
Need effective dates:
Device A
Office 1 until Jan 15
Office 2 after Jan 15
Imagine:
Queue server down
Database down
Need:
- Automatic retries
- Dead-letter queue
- Backups
- Event replay
tenants
branches
devices
device_heartbeats
employees
device_users
attendance_raw_logs
attendance_processed
attendance_exceptions
attendance_adjustments
shift_assignments
device_sync_jobs
audit_logs
Expect requests for:
- Geo-fenced attendance
- Mobile attendance
- Visitor attendance
- Contractor attendance
- Anti-passback
- Face liveness detection
- Multiple biometric methods
- Real-time attendance dashboard
- Device health dashboard
- Attendance correction workflow
- Approval workflow
- Overtime approval
- Grace periods
- Auto shift assignment
- Holiday calendars
- Multi-country payroll
Treat every punch as an immutable event.
Never let devices write directly into payroll or attendance summaries.
Store raw logs forever, make ingestion idempotent, process through queues, and derive attendance from raw events.
If you do those four things correctly, you can recover from almost every real-world failure: internet outages, duplicate pushes, device replacement, shift rule changes, payroll recalculation, and software bugs.