Salami Gateway provides a centralized attendance and biometric data collection API. On-premise sync agents push scan data from ZKTeco biometric devices to Salami, which stores the raw scans and distributes them to registered consumer systems (Hub, HimaHR, etc.) via HMAC-signed webhooks.
https://your-domain.com/api/attendance
Two authentication methods depending on the endpoint:
| Method | Used by | Header |
|---|---|---|
| Device token | Sync agents | Authorization: Bearer {device_token} |
| App token | Consumer apps | Authorization: Bearer {app_token} |
Unauthenticated endpoints (no token required):
GET /api/attendance/agent/downloads)Receive biometric scan data from a sync agent.
POST /api/attendance/scans/push
Authorization: Bearer {device_token}
Content-Type: application/json
Request body:
{
"device_token": "abc123...",
"scans": [
{
"uid": "1",
"timestamp": "2026-04-15 08:23:45",
"state": 0,
"type": 0
}
]
}
| Field | Type | Description |
|---|---|---|
device_token |
string | Device API token (also sent in Authorization header) |
scans[].uid |
string | Device user ID (mapped to employee by the consumer) |
scans[].timestamp |
string | Scan time in Y-m-d H:i:s format |
scans[].state |
int | 0 = check-in, 1 = check-out (often unreliable, consumers use chronological order) |
scans[].type |
int | 0 = fingerprint, 1 = password, 2 = card |
Response (200):
{
"message": "Scans received.",
"received": 150,
"device_name": "Office 3",
"batch_id": "a1b2c3d4-..."
}
Errors:
| Code | Reason |
|---|---|
| 401 | Invalid or inactive device token |
| 422 | Validation error (missing scans, etc.) |
Check if a device token is valid and the device is active.
GET /api/attendance/scans/status?device_token={token}
Authorization: Bearer {device_token}
Response (200):
{
"device_id": 5,
"name": "Office 3",
"is_active": true,
"last_synced_at": "2026-04-15T08:30:00+03:00",
"device_type": "zkteco"
}
Fetch stored scans for a date range. Used by consumer apps for polling (alternative to webhooks).
GET /api/attendance/scans?from=2026-04-01&to=2026-04-15
Authorization: Bearer {app_token}
| Parameter | Required | Description |
|---|---|---|
from |
Yes | Start date (Y-m-d) |
to |
Yes | End date (Y-m-d) |
device_token |
No | Filter by specific device |
page |
No | Pagination (default 1) |
Response (200): Paginated scan records.
Public endpoint returning sync agent binaries and setup instructions for each OS.
GET /api/attendance/agent/downloads
Response (200):
{
"agent_version": "3.2",
"downloads": {
"windows": "/downloads/salami-sync-agent.exe",
"mac_intel": "/downloads/salami-sync-agent-mac-intel",
"mac_arm": "/downloads/salami-sync-agent-mac-arm64",
"linux": "/downloads/salami-sync-agent-linux-amd64"
},
"setup": {
"windows": {
"steps": ["1. Download .exe", "2. Create config.json", "3. Run"],
"example_config": { "..." }
}
}
}
Trigger a manual sync of attendance data from ZKTeco BioTime cloud.
POST /api/attendance/biotime/sync
Authorization: Bearer {app_token}
Response (200):
{
"message": "BioTime sync completed.",
"synced": 45,
"batch_id": "a1b2c3d4-...",
"device_name": "BioTime Cloud"
}
When scans are received (from agents or BioTime), Salami dispatches webhooks to all registered consumers.
{
"event": "scans.received",
"batch_id": "a1b2c3d4-...",
"device_token": "abc123...",
"device_name": "Office 3",
"scans": [
{
"uid": "1",
"timestamp": "2026-04-15 08:23:45",
"state": 0,
"type": 0
}
]
}
Content-Type: application/json
X-Salami-Signature: sha256={hmac_hash}
The X-Salami-Signature header contains an HMAC-SHA256 hash of the request body signed with the shared secret configured when registering the webhook.
PHP example:
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SALAMI_SIGNATURE'];
$expected = 'sha256=' . hash_hmac('sha256', $payload, $webhookSecret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('Invalid signature');
}
| Source | Method | Description |
|---|---|---|
| ZKTeco devices | Sync agent pushes to /scans/push |
On-premise UDP connection to biometric devices |
| BioTime cloud | Scheduled command or /biotime/sync |
REST API pull from ZKTeco BioTime SaaS |
| Manual entry | Future | Direct scan entry via API |