Intégrer une pointeuse ZKTeco dans une application : architecture, API BioTime et synchronisation
Intégration & API

Intégrer une pointeuse ZKTeco dans une application : architecture, API BioTime et synchronisation

Rodolphe Kouadio
Rodolphe Kouadio
21 septembre 2026
15 min de lecture

Retour d’expérience sur l’intégration d’une pointeuse ZKTeco avec BioTime : architecture, API, synchronisation, idempotence et gestion des données.

Intégrer une pointeuse ZKTeco dans une application : architecture, API BioTime et synchronisation

Retour d'expérience — Cet article présente les principaux enseignements d'une intégration réelle de pointeuses ZKTeco dans un système de gestion des présences, en utilisant BioTime comme middleware.
L'objectif n'est pas de fournir une implémentation prête à copier-coller, mais de montrer comment penser l'architecture, les données, la synchronisation et la résilience lorsqu'une application métier doit communiquer avec un système de pointage physique.

🎯 Pourquoi intégrer une pointeuse dans une application métier ?

Une pointeuse physique semble relativement simple : un employé badge, un événement est enregistré et l'entreprise doit ensuite savoir qui était présent, à quelle heure et pendant combien de temps.

En réalité, dès qu'on souhaite intégrer ces données dans une application métier, plusieurs problématiques apparaissent :

  • comment récupérer les événements ?
  • comment identifier correctement l'employé ?
  • comment éviter les doublons ?
  • que faire si la synchronisation échoue ?
  • comment gérer plusieurs pointages dans une même journée ?
  • comment gérer les erreurs retournées par l'API ?
  • comment reconstruire une présence à partir des événements bruts ?
  • comment remplacer ZKTeco par une autre marque plus tard ?

La difficulté n'est donc pas uniquement de faire un appel API.

Le véritable sujet est de construire une intégration fiable, idempotente et évolutive.

1. Comprendre l'architecture : l'application ne communique pas directement avec la pointeuse

La première décision importante est de comprendre le rôle de BioTime.

Les terminaux ZKTeco utilisent leur propre protocole de communication. Dans une architecture applicative classique, il est préférable de ne pas faire communiquer directement le backend métier avec chaque terminal.

L'architecture utilisée repose plutôt sur trois niveaux :

Pointeuse ZKTeco → BioTime → Backend de l'application

La pointeuse transmet ses événements à BioTime.

BioTime centralise ensuite les données et expose une API REST permettant au backend de les récupérer et de gérer certaines informations.

Le rôle de chaque composant

Pointeuse ZKTeco

  • capture les événements de pointage ;
  • transmet les événements à BioTime.

BioTime

  • centralise les terminaux ;
  • stocke les événements ;
  • gère les employés, départements et zones ;
  • expose les données via son API.

Backend métier

  • récupère les pointages ;
  • identifie les employés ;
  • stocke les événements dans sa propre base ;
  • calcule les présences ;
  • alimente les tableaux de bord ;
  • peut également envoyer certaines informations vers BioTime.

Le flux général

Pointeuse → BioTime → API REST → Backend → Base de données → Calcul des présences

Cette séparation est importante : BioTime devient la passerelle entre le matériel et l'application métier.

2. L'API BioTime

BioTime expose plusieurs endpoints permettant d'interagir avec ses différentes ressources.

Authentification

Selon la version de BioTime, l'authentification peut utiliser un token JWT.

Endpoint principal

Méthode : POST

Endpoint :

/jwt-api-token-auth/

Paramètres

  • username
  • password

La réponse contient un token qui doit ensuite être transmis dans les requêtes suivantes.

L'en-tête utilisé dans l'intégration était :

Authorization : JWT {token}

Un point important : selon certaines versions de BioTime, l'endpoint d'authentification peut différer. Une stratégie de fallback peut donc être nécessaire.

Endpoint alternatif rencontré

/api-token-auth/

Cette différence entre versions est typiquement le genre de détail qui peut faire perdre beaucoup de temps lors d'une intégration.

3. Les endpoints essentiels

Voici les principaux endpoints utilisés dans l'intégration.

Fonction

Méthode

Endpoint

Lister les terminaux

GET

/iclock/api/terminals/

Détail d'un terminal

GET

/iclock/api/terminals/{id}/

Lister/créer les zones

GET / POST

/personnel/api/areas/

Lister/créer les départements

GET / POST

/personnel/api/departments/

Lister/créer/modifier les employés

GET / POST / PUT

/personnel/api/employees/

Récupérer les pointages

GET

/att/api/transactionReport/

Le point le plus important pour une application de gestion des présences reste :

/att/api/transactionReport/

C'est cet endpoint qui permet de récupérer les transactions de pointage nécessaires à la construction de la présence dans l'application.

4. Comprendre la pagination de BioTime

Les endpoints de liste sont paginés.

Une réponse peut notamment contenir :

  • count
  • next
  • previous
  • code
  • data

Le champ next est particulièrement important.

Il peut contenir directement l'URL complète permettant de récupérer la page suivante.

Il ne faut donc pas nécessairement reconstruire soi-même l'URL à partir d'un numéro de page.

Principe

Page actuelle → next → page suivante → next → etc.

Cette approche permet de parcourir toutes les données jusqu'à ce que next soit nul.

5. Attention : HTTP 200 ne signifie pas forcément succès

C'est l'un des pièges importants rencontrés lors de l'intégration.

BioTime peut retourner une réponse HTTP réussie tout en indiquant une erreur au niveau métier.

Il faut donc vérifier deux niveaux :

Niveau 1 — HTTP

Le statut de la réponse.

Niveau 2 — BioTime

Le champ :

response.data.code

Une réponse HTTP 200 avec un code métier différent de 0 doit être considérée comme une erreur.

Principe

HTTP 200 ≠ nécessairement opération réussie

Cette distinction est importante lorsqu'on développe un connecteur fiable.

6. Concevoir le modèle de données : conserver les événements bruts

L'une des décisions d'architecture les plus importantes a été de ne pas enregistrer directement une présence sous la forme :

Entrée : 08h00
Sortie : 17h00

Cette information est déjà une interprétation.

La source de vérité doit plutôt être le journal des événements bruts.

Exemple de données conservées

Un événement peut contenir :

  • employé ;
  • date et heure du pointage ;
  • source ;
  • identifiant de transaction externe ;
  • identifiant employé provenant de BioTime ;
  • type de pointage lorsqu'il est disponible ;
  • métadonnées provenant de la source.

On obtient alors une architecture de type :

Punch → Projection → Attendance

Le Punch représente l'événement brut.

L'Attendance représente la présence calculée.

7. Pourquoi séparer les événements des présences ?

Cette séparation apporte plusieurs avantages.

Recalcul

Si les règles de calcul changent, les présences peuvent être recalculées à partir des événements historiques.

Correction

Si un événement est ajouté ou corrigé, la journée concernée peut être recalculée.

Audit

Les événements originaux restent disponibles pour comprendre comment une présence a été construite.

Multi-source

Une même journée peut recevoir des événements provenant :

  • d'une pointeuse ZKTeco ;
  • d'une application mobile ;
  • d'une autre pointeuse ;
  • d'un autre fournisseur.

L'application peut ensuite calculer une présence globale à partir de ces différentes sources.

8. L'idempotence : éviter les doublons

Lorsqu'on synchronise régulièrement une API externe, il faut partir du principe qu'une même donnée peut être récupérée plusieurs fois.

Une synchronisation peut être relancée :

  • après une erreur ;
  • après un redémarrage ;
  • lors d'un rattrapage ;
  • parce qu'une période chevauche une précédente synchronisation.

Il faut donc rendre l'import idempotent.

La clé naturelle

Chaque transaction BioTime possède généralement un identifiant.

Une clé peut être construite autour de cet identifiant, par exemple :

zkteco:{transactionId}

Cette clé doit être unique dans la base.

Ainsi :

Même événement reçu deux fois → une seule donnée enregistrée.

C'est une décision particulièrement importante pour les systèmes de synchronisation.

9. Le problème du mapping des employés

La synchronisation des pointages n'est utile que si l'application sait à quel employé appartient chaque événement.

C'est l'un des problèmes les plus délicats.

Dans les données de transaction utilisées, le champ le plus fiable pour effectuer le rapprochement était :

emp_code

Il correspond au matricule utilisé côté BioTime.

Stratégie

L'application conserve une correspondance entre :

Employé interne ↔ identifiant BioTime

Lorsqu'un événement arrive, le backend recherche l'employé correspondant au emp_code.

Pourquoi ne pas dépendre uniquement de emp_id ?

Dans les données rencontrées, emp_id n'était pas toujours suffisamment fiable ou disponible pour servir de clé principale.

La stratégie retenue privilégiait donc emp_code.

10. Attention aux valeurs nulles ou absentes

Lorsqu'on réalise un mapping automatique, une erreur classique consiste à lancer une recherche avec une valeur absente.

Par exemple :

  • undefined
  • null
  • chaîne vide

Une requête mal construite peut alors retourner un mauvais enregistrement.

La règle est simple :

Toujours valider l'identifiant avant de rechercher l'employé correspondant.

Ce genre de validation semble trivial, mais devient particulièrement important dans les systèmes qui traitent des milliers d'événements automatiquement.

11. Les données sensibles et le chiffrement

Les identifiants employés peuvent être considérés comme des données sensibles selon le contexte de l'application.

Lorsque les données sont chiffrées en base, un problème supplémentaire apparaît :

Comment rechercher une donnée chiffrée ?

Pour certains champs nécessitant une recherche exacte, une stratégie de chiffrement déterministe peut être utilisée.

L'objectif est qu'une même valeur produise une représentation chiffrée permettant de retrouver l'enregistrement correspondant.

Il faut cependant considérer attentivement les implications de sécurité de cette approche et ne pas utiliser le chiffrement déterministe comme solution universelle.

12. Synchroniser les pointages

Le processus de synchronisation peut être résumé ainsi :

Étape 1 — Identifier les périmètres à synchroniser

Par exemple :

  • départements ;
  • zones ;
  • sites ;
  • terminaux concernés.

Étape 2 — Interroger BioTime

Utiliser :

GET /att/api/transactionReport/

avec les paramètres de période et, si nécessaire, les paramètres de filtrage disponibles sur l'installation.

Étape 3 — Identifier l'employé

Utiliser notamment :

emp_code

Étape 4 — Normaliser la date et l'heure

Les formats de date peuvent varier.

Étape 5 — Enregistrer l'événement

Utiliser une clé unique permettant de garantir l'idempotence.

Étape 6 — Identifier les journées impactées

Pour chaque événement importé, identifier :

employé + journée

Étape 7 — Recalculer la présence

Recalculer la journée à partir de l'ensemble des événements disponibles.

13. Le problème des horaires

Un autre détail important concerne le format des heures.

Selon les données retournées, l'heure peut être fournie sous une forme telle que :

HH:MM

ou sous la forme d'une date/heure complète.

Lorsqu'une heure seule est fournie, elle doit être associée à la date correspondante.

Exemple conceptuel

14:30

doit être associé à :

2026-09-18

pour obtenir un événement complet.

14. Les fuseaux horaires

Les systèmes de pointage sont particulièrement sensibles aux fuseaux horaires.

Une différence de timezone peut transformer :

23:30

en :

00:30 du jour suivant

et donc affecter complètement le calcul de présence.

Il faut donc vérifier :

  • timezone du serveur BioTime ;
  • timezone du backend ;
  • timezone du terminal ;
  • timezone de la base ;
  • format utilisé lors de la conversion.

Règle

Une date de pointage doit toujours être interprétée avec une stratégie de timezone clairement définie.

15. Recalculer la présence à partir des événements

Une fois les événements importés, l'application peut produire une projection de présence.

Par exemple :

Premier pointage → entrée

Dernier pointage → sortie

Les événements intermédiaires peuvent correspondre à :

  • pause ;
  • reprise ;
  • autres mouvements.

La logique exacte dépend toutefois des règles métier de l'entreprise.

C'est justement pourquoi il est préférable de conserver les événements bruts plutôt que de stocker uniquement une interprétation.

16. Envoyer des données vers BioTime

L'intégration peut également fonctionner dans l'autre sens.

L'application peut créer ou modifier certaines données dans BioTime.

Cela permet notamment de synchroniser :

  • zones ;
  • départements ;
  • employés.

Zones

Endpoint :

POST /personnel/api/areas/

Paramètres principaux utilisés :

  • area_name
  • area_code

Départements

Endpoint :

POST /personnel/api/departments/

Paramètres principaux :

  • dept_code
  • dept_name
  • parent_dept

Employés

Endpoint :

POST /personnel/api/employees/

Les informations nécessaires peuvent notamment inclure :

  • emp_code
  • department
  • area
  • card_no

L'identifiant retourné par BioTime doit ensuite être conservé dans l'application pour maintenir la correspondance entre les deux systèmes.

17. Une particularité de la création des employés

Une particularité rencontrée lors de l'intégration est que certaines informations d'employé ne sont pas toujours correctement prises en compte lors de la création initiale.

Une stratégie plus robuste consiste alors à séparer l'opération :

Étape 1

Créer l'employé avec les informations essentielles.

Étape 2

Récupérer son identifiant BioTime.

Étape 3

Compléter ou modifier les informations avec :

PUT /personnel/api/employees/{id}/

Cette stratégie permet également de mieux gérer les différences de comportement entre versions de BioTime.

18. Gérer les réponses imprévisibles

Une API d'un système métier ancien ou installé sur site peut parfois avoir des comportements différents de ceux attendus.

Lors des tests, plusieurs formats peuvent être rencontrés :

Réponse JSON enveloppée

Avec des informations comme :

  • code
  • msg
  • data

Réponse JSON directe

Les données sont directement présentes dans la réponse.

Réponse HTML

Dans certains cas d'erreur serveur, le système peut retourner une page HTML plutôt qu'une réponse JSON.

Le backend doit donc être suffisamment défensif pour vérifier le format réel de la réponse avant de la traiter.

19. Synchronisation automatique

Une synchronisation manuelle n'est pas suffisante pour un système de présence.

Le système doit pouvoir fonctionner automatiquement.

Un mécanisme de scheduling permet par exemple de lancer une synchronisation quotidiennement.

Mais le vrai sujet n'est pas uniquement :

« Comment lancer le cron ? »

Le vrai sujet est :

Que se passe-t-il si le serveur était indisponible au moment prévu ?

20. Le rattrapage

Pour rendre la synchronisation résiliente, plusieurs mécanismes peuvent être combinés :

  • synchronisation planifiée ;
  • synchronisation de rattrapage ;
  • synchronisation au démarrage ;
  • période de récupération légèrement chevauchante ;
  • idempotence.

Ainsi, si une synchronisation échoue, une exécution suivante peut récupérer les données manquantes sans créer de doublons.

C'est ici que l'idempotence et le rattrapage fonctionnent ensemble.

21. Les problèmes réellement rencontrés

Voici les principaux problèmes rencontrés lors de l'intégration :

Problème

Conséquence

Approche retenue

code différent de 0 malgré HTTP 200

Erreur métier ignorée

Vérifier la réponse BioTime

Réponse HTML

Impossible de parser le JSON

Vérifier le type de réponse

Champs ignorés lors du POST

Employé incomplet

Compléter avec PUT

punch_time sans date

Mauvais timestamp

Combiner avec la date

emp_id absent

Employé impossible à retrouver

Utiliser emp_code

Timeout

Synchronisation interrompue

Timeout adapté

Pagination

Données manquantes

Suivre next

Identifiant absent

Mauvais mapping

Validation avant recherche

Token expiré

Requêtes refusées

Réauthentification

Cette partie est particulièrement intéressante parce qu'elle montre la différence entre lire une documentation API et intégrer réellement un système.

22. Concevoir une architecture multi-fournisseurs

Une question apparaît rapidement lorsqu'on construit un système de gestion des présences :

Que se passe-t-il si demain l'entreprise utilise Hikvision au lieu de ZKTeco ?

Une mauvaise architecture conduirait à mettre toute la logique ZKTeco directement dans le cœur de l'application.

Une meilleure approche consiste à isoler chaque fournisseur derrière un adapter.

Le concept de provider

Le cœur de l'application manipule un contrat générique.

Par exemple :

Provider

  • key
  • label
  • testConnection
  • listAvailableDevices
  • fetchStatus
  • syncEvents
  • listPersons
  • pushPerson
  • removePerson

Le cœur de l'application ne connaît alors pas les détails propres à ZKTeco.

Il demande simplement :

« Synchronise les événements de ce terminal. »

Le provider ZKTeco sait ensuite comment communiquer avec BioTime.

23. Pourquoi cette architecture est intéressante ?

Cette séparation permet d'ajouter progressivement :

  • ZKTeco ;
  • Hikvision ;
  • Suprema ;
  • d'autres fournisseurs.

sans modifier toute la logique métier.

L'application devient ainsi :

Core métier → Provider → Système externe

plutôt que :

Core métier → logique ZKTeco partout

C'est une différence importante lorsqu'un projet doit évoluer.

24. Checklist avant mise en production

Infrastructure

  • BioTime accessible depuis le backend.
  • Terminaux correctement configurés.
  • Communication terminal → BioTime fonctionnelle.
  • Compte API dédié.
  • Variables d'environnement configurées.
  • Timeout adapté.

Données

  • Identifiants employés correctement associés.
  • Départements synchronisés.
  • Sites/zones synchronisés.
  • Index d'idempotence configuré.
  • Stratégie de chiffrement définie si nécessaire.

Synchronisation

  • Synchronisation automatique.
  • Mécanisme de rattrapage.
  • Protection contre les doubles exécutions.
  • Logs suffisamment détaillés.
  • Gestion des erreurs.
  • Test complet du flux.

25. Tester le flux de bout en bout

Avant de considérer l'intégration comme opérationnelle, il faut vérifier toute la chaîne :

Badge physique

↓

Pointeuse ZKTeco

↓

BioTime

↓

API BioTime

↓

Backend

↓

Journal des pointages

↓

Calcul de présence

↓

Tableau de bord

Un test réussi uniquement au niveau de l'API ne garantit donc pas que l'intégration complète fonctionne.

26. Ce que cette intégration m'a appris

Cette intégration m'a surtout permis de travailler sur des problématiques qui dépassent le simple développement d'une API.

Elle m'a amené à travailler sur :

  • l'intégration entre logiciel et matériel ;
  • la synchronisation de données ;
  • l'idempotence ;
  • la modélisation des événements ;
  • le mapping entre systèmes ;
  • la gestion des erreurs ;
  • les problèmes de timezone ;
  • les tâches planifiées ;
  • le rattrapage ;
  • la conception d'architectures extensibles ;
  • la séparation entre logique métier et fournisseurs externes.

L'un des principaux enseignements est qu'une intégration réussie ne consiste pas simplement à faire fonctionner une requête.

Elle doit également rester fiable lorsque :

  • l'API est lente ;
  • le serveur redémarre ;
  • une donnée est reçue deux fois ;
  • une réponse est différente de celle attendue ;
  • un fournisseur change ;
  • une synchronisation échoue.

Conclusion

L'intégration d'une pointeuse ZKTeco avec une application métier repose finalement sur quelques principes essentiels :

1. Utiliser BioTime comme intermédiaire

Le backend métier ne doit pas avoir à gérer directement le protocole propriétaire des terminaux.

2. Conserver les événements bruts

Les pointages constituent la source de vérité. Les présences sont des données calculées.

3. Rendre la synchronisation idempotente

Une synchronisation peut être relancée sans créer de doublons.

4. Soigner le mapping des employés

L'identification correcte de l'employé est indispensable à toute la chaîne.

5. Gérer les erreurs au niveau métier

Un HTTP 200 ne signifie pas nécessairement que l'opération a réussi.

6. Prévoir le rattrapage

Une synchronisation automatique doit pouvoir récupérer les données après une interruption.

7. Isoler les fournisseurs

Une architecture basée sur des providers/adapters facilite l'ajout futur de nouvelles marques.

Le vrai défi d'une intégration de systèmes n'est pas de connecter deux APIs. C'est de construire une connexion suffisamment fiable pour fonctionner dans les conditions réelles d'une entreprise.
Besoin d’un développeur pour concrétiser ton projet ?

Si cet article t’a donné envie d’aller plus loin (audit, refonte, application, UI moderne), écris-moi et on en parle.

Tags

ZKTeco BioTime API REST Node.js MongoDB Architecture Backend Synchronisation Pointage Gestion des présences Idempotence API Integration
Rodolphe Kouadio

À propos de Rodolphe Kouadio

Développeur Full Stack passionné par les nouvelles technologies et le partage de connaissances.