# 📡 Updated Cloud API Webhook Format

## Cloud-to-Local Biometric Logs Mirror

Your cloud API webhook should now send data in this format:

---

## ✅ Correct Webhook Format (Updated)

```json
{
  "log_id": "cloud_uuid_or_id_12345",
  "device_id": 1,
  "pin": "111111",
  "punched_at": "2026-06-09 14:30:00",
  "status": 0,
  "verify_type": 1,
  "work_code": null
}
```

### Field Descriptions:

| Field | Type | Required | Description | Example |
|-------|------|----------|-------------|---------|
| `log_id` | string | ✅ Yes | Unique cloud API log identifier | `"cloud_log_abc123"` |
| `device_id` | integer | ✅ Yes | Device ID in local system | `1` |
| `pin` | string | ✅ Yes | Employee PIN (user identifier) | `"111111"` |
| `punched_at` | string | ✅ Yes | Punch timestamp (Y-m-d H:i:s) | `"2026-06-09 14:30:00"` |
| `status` | integer | ✅ Yes | Attendance type (0-5) | `0` (Check In) |
| `verify_type` | integer | ✅ Yes | Biometric type | `1` (Fingerprint) |
| `work_code` | integer | ❌ No | Optional work code | `null` |

---

## 📋 Status Values Reference

```
0 = Check In
1 = Check Out
2 = Break Out
3 = Break In
4 = OT In (Overtime In)
5 = OT Out (Overtime Out)
```

---

## 🔐 Verify Type Values Reference

```
0 = Password
1 = Fingerprint
2 = Card
15 = Face Recognition
```

---

## 🚨 IMPORTANT: Field Mapping Changes

| OLD Format | NEW Format | Why Changed |
|-----------|-----------|------------|
| `user_id` | `pin` | zkteco_attendance_logs uses PIN for user lookup |
| `timestamp` | `punched_at` | Matches existing table column name |
| `type` (string: "checkin") | `status` (int: 0-5) | Matches ZK Teco device standards |
| `biometric_type` (string) | `verify_type` (int: 0,1,2,15) | Matches ZK Teco standards |
| N/A | `log_id` | Required for idempotency & duplicate detection |

---

## 📤 Example Webhook Payload

```json
{
  "log_id": "zkteco_cloud_2026_06_09_143000_device_1_pin_111111",
  "device_id": 1,
  "pin": "111111",
  "punched_at": "2026-06-09 14:30:00",
  "status": 0,
  "verify_type": 1,
  "work_code": null
}
```

---

## 🔗 API Endpoint Details

### Webhook URL:
```
POST https://your-server.com/api/webhooks/biometric-logs
```

### Required Headers:
```
X-Webhook-Signature: <HMAC-SHA256>
X-Webhook-Timestamp: <ISO-8601>
Content-Type: application/json
```

### Signature Calculation:
```php
$payload = json_encode($data);
$secret = 'your_webhook_secret';
$timestamp = date('c'); // ISO 8601

$signature = hash_hmac('sha256', $payload, $secret);

// Headers
X-Webhook-Signature: $signature
X-Webhook-Timestamp: $timestamp
```

### Expected Responses:

**Success (200):**
```json
{
  "status": "received",
  "log_id": 123,
  "cloud_log_id": "zkteco_cloud_2026_06_09_143000..."
}
```
**Status Code:** 202 Accepted

**Duplicate (200):**
```json
{
  "status": "duplicate"
}
```
**Status Code:** 200 OK

**Validation Error (422):**
```json
{
  "error": "Validation failed",
  "messages": {
    "pin": ["The pin field is required."],
    "device_id": ["The device id must be an integer."]
  }
}
```
**Status Code:** 422 Unprocessable Entity

**Signature Error (401):**
```json
{
  "error": "Invalid signature"
}
```
**Status Code:** 401 Unauthorized

---

## ✅ Validation Rules

| Field | Validation |
|-------|-----------|
| `log_id` | Required, string, unique |
| `device_id` | Required, integer |
| `pin` | Required, string |
| `punched_at` | Required, datetime format (Y-m-d H:i:s) |
| `status` | Required, integer 0-5 |
| `verify_type` | Required, integer 0, 1, 2, or 15 |
| `work_code` | Optional, integer |

---

## 🧪 Test Examples

### Using curl:

```bash
PAYLOAD='{
  "log_id": "test_123",
  "device_id": 1,
  "pin": "111111",
  "punched_at": "2026-06-09 14:30:00",
  "status": 0,
  "verify_type": 1,
  "work_code": null
}'

SECRET="your_webhook_secret"
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')

curl -X POST https://your-server.com/api/webhooks/biometric-logs \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Signature: $SIGNATURE" \
  -H "X-Webhook-Timestamp: $TIMESTAMP" \
  -d "$PAYLOAD"
```

### Using Python:

```python
import requests
import hmac
import hashlib
import json
from datetime import datetime

payload = {
    "log_id": "test_123",
    "device_id": 1,
    "pin": "111111",
    "punched_at": "2026-06-09 14:30:00",
    "status": 0,
    "verify_type": 1,
    "work_code": None
}

secret = "your_webhook_secret"
timestamp = datetime.utcnow().isoformat() + "Z"
payload_str = json.dumps(payload)

signature = hmac.new(
    secret.encode(),
    payload_str.encode(),
    hashlib.sha256
).hexdigest()

headers = {
    "Content-Type": "application/json",
    "X-Webhook-Signature": signature,
    "X-Webhook-Timestamp": timestamp
}

response = requests.post(
    "https://your-server.com/api/webhooks/biometric-logs",
    json=payload,
    headers=headers
)

print(response.json())
```

---

## 🔄 Processing Flow

```
1. Webhook received
   ↓
2. Signature verified (HMAC-SHA256)
   ↓
3. Timestamp validated (within 10 minutes)
   ↓
4. Payload validated
   ↓
5. Duplicate check (by log_id)
   ↓
6. AttendanceLog record created (sync_status: pending)
   ↓
7. Job queued for async processing
   ↓
8. Return 202 immediately
   ↓
9. Background job validates and syncs
   ↓
10. sync_status updated to success/failed
```

---

## 💾 Database Columns

Logs are stored in `zkteco_attendance_logs` with these tracking fields:

- `cloud_log_id` - Your log ID (for idempotency)
- `cloud_sync_time` - When it was synced
- `sync_status` - pending, success, or failed
- `retry_count` - Number of attempts
- `error_message` - Error details if failed

---

## ❓ FAQ

**Q: What if the same log_id is sent twice?**
A: It's treated as a duplicate. Returns 200 with `{"status": "duplicate"}`. No new record created.

**Q: How long does processing take?**
A: Webhook returns immediately (202). Background job processes in ~14ms per log.

**Q: What happens if validation fails?**
A: Log is marked as `sync_status: failed` with error message in `error_message` column.

**Q: How many retries?**
A: Up to 3 attempts with exponential backoff (1min → 5min → 15min).

**Q: Can I use my own user_id field?**
A: No, the system uses PIN for user lookup to match ZK Teco device standards.

---

## 🚀 Ready to Deploy

Update your cloud API to send data in this format and you're ready to go!

For setup details, see: **REFACTOR_SUMMARY.md**
