Browse software

ZKTeco attendance API: send punches to payroll and HR

Stop copying attendance into payroll by hand. Two ways to move it automatically: ask for it with a REST API, or be told the moment it happens with webhooks.

Updated 4 October 2026 · 6 min read

Why the machine itself is not the API

Attendance machines are built to talk to one server, and their protocols are awkward to use from another app. The clean setup is: machines push punches to an attendance server (ADMS push mode), and the attendance server offers a proper API to your payroll, HR or website. Your other apps never touch the machines.

Option 1 — read it with a REST API

Your app asks when it needs data. With PunchSync the API looks like this:

ItemValue
Addresshttps://your-domain.com/api/v1
Sign inHeader Authorization: Bearer YOUR_API_KEY
Limit120 requests a minute per key
FormatJSON
EndpointReturns
GET /employeesEmployees, page by page
GET /punchesRaw punches — send since_id to keep a copy in sync, or from / to dates (up to 31 days)
GET /attendanceWorked-out days: status, first in, last out, late and overtime minutes
GET /devicesYour machines
GET /departmentsYour departments
curl -H "Authorization: Bearer YOUR_API_KEY" https://your-domain.com/api/v1/attendance

Payroll tip: use /attendance, not raw punches. It already applies shifts, holidays and leave, so late and overtime minutes match the reports your managers see.

Option 2 — be told with webhooks

Instead of asking every few minutes, give the attendance server your app's address. New punches arrive as a POST with a JSON body — up to 100 punches at a time, in order.

Check the signature before you trust it

Each webhook has its own secret. The X-PunchSync-Signature header looks like t=1790000000,v1=5f3a…, where v1 is the HMAC-SHA256 (hex) of the time, a dot, and the raw body. In PHP:

<?php
$secret = 'YOUR_WEBHOOK_SECRET';
$body   = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_PUNCHSYNC_SIGNATURE'] ?? ''), $sig);
$expected = hash_hmac('sha256', ($sig['t'] ?? '') . '.' . $body, $secret);
$fresh    = abs(time() - (int) ($sig['t'] ?? 0)) < 300;
if (! $fresh || ! hash_equals($expected, $sig['v1'] ?? '')) {
    http_response_code(400);
    exit;
}
// Trusted: json_decode($body, true)

Refusing old timestamps stops someone from replaying a copied message.

Which one should you use?

REST APIWebhooks
Best forMonthly payroll runs, reports, syncing employeesLive dashboards, instant alerts, door or canteen systems
Your app needsTo call outA public address to receive
SpeedWhen you askSeconds after the punch

Many teams use both: webhooks for live updates, and the API once a month to double-check before payroll.

Hosting note: webhooks are sent by a scheduled job, so the server's cron must run every minute — see installing on cPanel.
Topics:APIWebhooksPayrollHR integrationZKTeco
Everything above is built into PunchSync — API keys, signed webhooks with retries, and an in-app API guide with real example answers and a Postman collection. Try the live preview.

Related guides

ZKTeco and eSSL are trademarks of their respective owners.