shaykhnazar/hikvision-isapi

Laravel package for Hikvision ISAPI Face Recognition Terminals integration

Maintainers

Package info

github.com/Shaykhnazar/hikvision-isapi

pkg:composer/shaykhnazar/hikvision-isapi

Transparency log

Statistics

Installs: 345

Dependents: 0

Suggesters: 0

Stars: 27

Open Issues: 0

v2.0.0-beta.3 2026-08-26 19:32 UTC

README

Latest Version License: MIT PHP Version Laravel Version

A clean, modern Laravel package for integrating with Hikvision ISAPI Face Recognition Terminals and access control devices. Supports multi-device management, universal device providers (config, database, API), multi-tenant architectures, and real-time event webhooks.

Features

  • Real-Time Event Webhooks: Configure devices to push events to your API endpoints (v1.4.0+)
  • Multi-Device Support: Manage unlimited Hikvision terminals simultaneously (v1.2.0+)
  • Universal Device Providers: Load devices from config, database, API, or custom sources (v1.3.0+)
  • Multi-Tenant Ready: Tenant-scoped device loading with runtime registration (v1.3.0+)
  • Device Management: Get device info, status, and capabilities
  • Person Management: Add, update, search, and delete persons
  • Card Management: Handle access cards with full CRUD operations
  • Face Recognition: Upload and manage face images with advanced search (v1.1.0+)
  • Fingerprint Support: Register and manage fingerprints
  • Access Control: Control doors remotely
  • Event Handling: Search and subscribe to access events
  • Event Notifications: HTTP webhooks for real-time event delivery (v1.4.0+)
  • Clean Architecture: SOLID principles, dependency injection, contracts
  • Type Safety: PHP 8.2+ features (enums, readonly properties)
  • Performance: Built-in caching with configurable TTL (v1.3.0+)
  • XML/JSON Support: Automatic format detection and conversion (v1.4.0+)
  • Laravel 12/13: Supports Laravel 12.x and 13.x with service provider and facade

Table of Contents

Requirements

  • PHP ^8.2
  • Laravel / illuminate/support ^12.0 || ^13.0
  • PHP ^8.3 is required when installing with Laravel 13
  • Guzzle HTTP ^7.8
  • ext-curl
  • ext-json
  • ext-simplexml
  • ext-libxml

Installation

Install the package via Composer:

composer require shaykhnazar/hikvision-isapi

Publish the configuration file:

php artisan vendor:publish --tag=hikvision-config

Configuration

Add these environment variables to your .env file:

HIKVISION_IP=192.168.1.100
HIKVISION_PORT=80
HIKVISION_USERNAME=admin
HIKVISION_PASSWORD=your_password
HIKVISION_PROTOCOL=http
HIKVISION_TIMEOUT=30
HIKVISION_VERIFY_SSL=false
HIKVISION_FORMAT=json

Configuration File

The published config/hikvision.php supports multiple devices:

return [
    'default' => env('HIKVISION_DEFAULT_DEVICE', 'primary'),

    'devices' => [
        'primary' => [
            'ip' => env('HIKVISION_IP', '192.168.1.100'),
            'port' => env('HIKVISION_PORT', 80),
            'username' => env('HIKVISION_USERNAME', 'admin'),
            'password' => env('HIKVISION_PASSWORD'),
            'protocol' => env('HIKVISION_PROTOCOL', 'http'),
            'timeout' => env('HIKVISION_TIMEOUT', 30),
            'verify_ssl' => env('HIKVISION_VERIFY_SSL', false),
        ],
    ],

    'format' => env('HIKVISION_FORMAT', 'json'),

    'logging' => [
        'enabled' => env('HIKVISION_LOGGING', true),
        'channel' => env('HIKVISION_LOG_CHANNEL', 'stack'),
    ],
];

Probing a device

Terminals differ. Capacity limits, which endpoints exist, and the exact subStatusCode behind each refusal all vary by model and firmware, and the documentation does not say. bin/hikvision-probe asks a device directly and writes the answers to a JSON file:

HIKVISION_PASSWORD='...' php vendor/bin/hikvision-probe --host=192.168.1.100
Capabilities
  ok   device_info
  ok   user_info
  no   fingerprint — HTTP 403
Person paging
  ok   page 1 — 10 rows, status MORE
  ok   page 2 — 4 rows, status OK
Refusals
  ok   bad_credentials — HTTP 401, subStatusCode badAuthorization
  ok   unknown_endpoint — HTTP 404, subStatusCode notSupport

Two things it guarantees, both enforced by tests:

It never writes. Every request is a read, including the ones that provoke refusals. It is safe to run against a terminal a business depends on.

It never carries anybody out. The person list is read for its shape — row counts, paging fields, what a row's keys are called. Field names are kept, field values are not, and the address you probed is not in the report either. What the file does contain is the device's own identity and behaviour, which is the point of it.

The report also lists what it could not answer and why — a file with ten answers and no mention of the eleventh question reads as complete.

If you run this against a model that is not in the compatibility notes yet, the report is exactly what a pull request adding it needs.

Quick Start

Check Device Status

use Shaykhnazar\HikvisionIsapi\Services\DeviceService;

$deviceService = app(DeviceService::class);

if ($deviceService->isOnline()) {
    $info = $deviceService->getInfo();
    echo "Device Model: " . $info['DeviceInfo']['model'];
}

Add a Person

use Shaykhnazar\HikvisionIsapi\Services\PersonService;
use Shaykhnazar\HikvisionIsapi\DTOs\Person;
use Shaykhnazar\HikvisionIsapi\Enums\UserType;

$personService = app(PersonService::class);

$person = new Person(
    employeeNo: 'EMP001',
    name: 'John Doe',
    userType: UserType::NORMAL,
    validEnabled: true,
    beginTime: now()->toISOString(),
    endTime: now()->addYear()->toISOString(),
    doorRight: '1',
    rightPlan: [
        ['doorNo' => 1, 'planTemplateNo' => '1']
    ]
);

$response = $personService->add($person);

Upload Face Image

use Shaykhnazar\HikvisionIsapi\Services\FaceService;

$faceService = app(FaceService::class);

// Read image file
$imageData = file_get_contents('path/to/photo.jpg');
$imageBase64 = base64_encode($imageData);

// Upload face (person must exist first)
$response = $faceService->uploadFace('EMP001', $imageBase64, 1);

Search and Manage Face Data

use Shaykhnazar\HikvisionIsapi\Services\FaceService;

$faceService = app(FaceService::class);

// Search face data with pagination
$results = $faceService->searchFace(
    page: 0,
    maxResults: 30,
    faceLibType: 'blackFD',
    fdid: 1,
    fpid: '31903791410044'
);

// Upload face data record with image file
$imageContent = file_get_contents('face.jpg');
$response = $faceService->uploadFaceDataRecord(
    fdid: 1,
    fpid: '31903791410044',
    imageContent: $imageContent,
    faceLibType: 'blackFD'
);

// Delete face search data
$faceService->deleteFaceSearch(fdid: 1, faceLibType: 'blackFD');

Search Persons

$personService = app(PersonService::class);

// Get total count
$total = $personService->count();

// Walk the whole list. One search session, and it stops when the device says
// there is nothing more — which a single search() call cannot know.
foreach ($personService->all() as $person) {
    echo "{$person->employeeNo}: {$person->name}\n";
}

// A single page, when that is genuinely all you want.
$persons = $personService->search(page: 0, maxResults: 30);

Paginating by hand? Pass the same $searchId to every call. ISAPI treats it as the identity of a search session, and changing it mid-walk makes the device start over — silently. all() handles this for you.

Add Card

use Shaykhnazar\HikvisionIsapi\Services\CardService;
use Shaykhnazar\HikvisionIsapi\DTOs\Card;

$cardService = app(CardService::class);

$card = new Card(
    employeeNo: 'EMP001',
    cardNo: '1234567890',
    cardType: 'normal'
);

$response = $cardService->add($card);

Control Doors

use Shaykhnazar\HikvisionIsapi\Services\AccessControlService;

$accessControl = app(AccessControlService::class);

// Open door
$accessControl->openDoor(1);

// Get door status
$status = $accessControl->getDoorStatus(1);

// Close door
$accessControl->closeDoor(1);

Search Events

use Shaykhnazar\HikvisionIsapi\Services\EventService;

$eventService = app(EventService::class);

$events = $eventService->search([
    'major' => 5,  // Access control
    'minor' => 75, // Face recognition
    'startTime' => now()->subDay()->toISOString(),
    'endTime' => now()->toISOString(),
], page: 0, maxResults: 50);

Event Webhooks & Notifications (v1.4.0+)

Configure Hikvision devices to automatically push events (access granted, face recognized, etc.) to your application's webhook endpoint in real-time. This eliminates the need for polling and provides instant event notifications.

Configure Webhook Endpoint

The EventNotificationService allows you to configure HTTP event notifications on Hikvision devices:

use Shaykhnazar\HikvisionIsapi\Services\EventNotificationService;

$eventNotificationService = app(EventNotificationService::class);

Simple Webhook Setup

The easiest way to set up a webhook is using the configureWebhook() method:

// Configure device to send all events to your webhook endpoint
$webhookUrl = 'https://your-api.com/api/webhooks/hikvision/events';

$response = $eventNotificationService->configureWebhook(
    webhookUrl: $webhookUrl,
    hostId: 1 // Host ID (1-8), default is 1
);

// Enable the webhook
$eventNotificationService->enableHttpHost(1);

Advanced Webhook Configuration

For more control over webhook configuration, use configureHttpHost():

$response = $eventNotificationService->configureHttpHost(
    url: 'https://your-api.com/api/webhooks/hikvision/events',
    id: 1,                      // Host ID (1-8)
    protocol: 'HTTPS',          // HTTP or HTTPS
    port: 443,                  // Port number
    httpAuthType: 'basic',      // none, basic, or digest
    username: 'webhook_user',   // Optional: for authentication
    password: 'webhook_pass',   // Optional: for authentication
    eventTypes: [               // Optional: specific event types (empty = all events)
        'AccessControllerEvent',
        'VideoMotion',
        'linedetection'
    ]
);

Managing Webhook Hosts

// Get webhook configuration
$config = $eventNotificationService->getHttpHost(1);

// Get all webhook configurations (supports up to 8 hosts)
$allConfigs = $eventNotificationService->getAllHttpHosts();

// Enable webhook
$eventNotificationService->enableHttpHost(1);

// Disable webhook
$eventNotificationService->disableHttpHost(1);

// Remove webhook configuration
$eventNotificationService->removeHttpHost(1);

// Get notification capabilities
$capabilities = $eventNotificationService->getCapabilities();

Testing Webhooks

Test if your webhook endpoint is reachable from the device:

// Send a test event to configured webhook
$response = $eventNotificationService->testHttpNotification(1);

Receiving Webhook Events

Create a controller to receive webhook events from Hikvision devices:

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;

class HikvisionWebhookController extends Controller
{
    /**
     * Receive events from Hikvision devices
     */
    public function handleEvent(Request $request)
    {
        // Hikvision sends events as XML
        $xmlData = $request->getContent();

        // Parse XML to array
        $xml = simplexml_load_string($xmlData);
        $event = json_decode(json_encode($xml), true);

        // Log the event
        Log::info('Hikvision Event Received', $event);

        // Process event based on type
        $eventType = $event['eventType'] ?? null;

        match($eventType) {
            'AccessControllerEvent' => $this->handleAccessControl($event),
            'faceDetection' => $this->handleFaceDetection($event),
            'VideoMotion' => $this->handleMotionDetection($event),
            default => Log::warning('Unknown event type', ['type' => $eventType])
        };

        // Return 200 OK to acknowledge receipt
        return response('OK', 200);
    }

    protected function handleAccessControl(array $event)
    {
        // Example: Handle access control event
        $employeeNo = $event['employeeNoString'] ?? null;
        $cardNo = $event['cardNo'] ?? null;
        $doorNo = $event['doorNo'] ?? null;

        Log::info('Access Control Event', [
            'employee' => $employeeNo,
            'card' => $cardNo,
            'door' => $doorNo,
            'time' => $event['dateTime'] ?? now()
        ]);

        // Your business logic here
        // - Record attendance
        // - Send notifications
        // - Update access logs
    }

    protected function handleFaceDetection(array $event)
    {
        // Handle face detection event
        Log::info('Face Detection Event', $event);
    }

    protected function handleMotionDetection(array $event)
    {
        // Handle motion detection event
        Log::info('Motion Detection Event', $event);
    }
}

Route setup:

// routes/api.php
Route::post('webhooks/hikvision/events', [HikvisionWebhookController::class, 'handleEvent'])
    ->name('hikvision.webhook');

Multi-Device Webhook Setup

Configure webhooks for multiple devices:

use Shaykhnazar\HikvisionIsapi\Facades\Hikvision;
use Shaykhnazar\HikvisionIsapi\Services\EventNotificationService;

$devices = ['entrance', 'exit', 'canteen'];
$webhookUrl = 'https://your-api.com/api/webhooks/hikvision/events';

foreach ($devices as $deviceName) {
    $client = Hikvision::device($deviceName);
    $eventService = new EventNotificationService($client);

    // Configure webhook with device name in URL for identification
    $deviceWebhookUrl = $webhookUrl . '?device=' . $deviceName;

    $eventService->configureWebhook($deviceWebhookUrl);
    $eventService->enableHttpHost(1);

    Log::info("Webhook configured for device: {$deviceName}");
}

Webhook Security

Protect your webhook endpoint:

// Add authentication to webhook configuration
$eventNotificationService->configureHttpHost(
    url: 'https://your-api.com/api/webhooks/hikvision/events',
    id: 1,
    protocol: 'HTTPS',
    port: 443,
    httpAuthType: 'basic',
    username: env('HIKVISION_WEBHOOK_USER'),
    password: env('HIKVISION_WEBHOOK_PASSWORD')
);

// In your controller, validate the credentials
public function handleEvent(Request $request)
{
    // Laravel automatically handles basic auth with middleware
    // Or manually validate:
    if ($request->getUser() !== env('HIKVISION_WEBHOOK_USER') ||
        $request->getPassword() !== env('HIKVISION_WEBHOOK_PASSWORD')) {
        return response('Unauthorized', 401);
    }

    // Process event...
}

Webhook Event Examples

Access Control Event (Face Recognition):

<EventNotificationAlert>
    <eventType>AccessControllerEvent</eventType>
    <eventState>active</eventState>
    <eventDescription>Access Control Event</eventDescription>
    <dateTime>2025-10-30T14:30:00Z</dateTime>
    <ActivePost>
        <eventType>AccessControllerEvent</eventType>
        <employeeNoString>EMP001</employeeNoString>
        <name>John Doe</name>
        <cardNo>1234567890</cardNo>
        <doorNo>1</doorNo>
        <swipeResult>success</swipeResult>
    </ActivePost>
</EventNotificationAlert>

Motion Detection Event:

<EventNotificationAlert>
    <eventType>VideoMotion</eventType>
    <eventState>active</eventState>
    <dateTime>2025-10-30T14:35:00Z</dateTime>
</EventNotificationAlert>

Using the Facade

use Shaykhnazar\HikvisionIsapi\Facades\HikvisionIsapi;

// Get device info
$info = HikvisionIsapi::get('/ISAPI/System/deviceInfo');

// Custom endpoint
$response = HikvisionIsapi::post('/ISAPI/CustomEndpoint', [
    'key' => 'value'
]);

Services Overview

DeviceService

$deviceService = app(DeviceService::class);

$deviceService->getInfo();           // Get device information
$deviceService->getCapabilities();   // Get device capabilities
$deviceService->getStatus();         // Get device status
$deviceService->isOnline();          // Check if device is online

PersonService

$personService = app(PersonService::class);

$personService->add(Person $person);              // Add person
$personService->update(Person $person);           // Update person
$personService->apply(Person $person);            // Apply person changes
$personService->all(int $pageSize);                // Walk every person (generator)
$personService->search(int $page, int $maxResults, ?string $searchId); // One page
$personService->delete(array $employeeNos);       // Delete persons
$personService->count();                          // Count persons
$personService->getCapabilities();                // Get capabilities

CardService

$cardService = app(CardService::class);

$cardService->add(Card $card);                    // Add card
$cardService->update(Card $card);                 // Update card
$cardService->search(...);                        // Search cards
$cardService->delete(array $employeeNos);         // Delete cards
$cardService->deleteAll();                        // Delete all cards
$cardService->count(?string $employeeNo);         // Count cards
$cardService->batchAdd(array $cards);             // Batch add cards

FaceService

$faceService = app(FaceService::class);

$faceService->uploadFace(string $employeeNo, string $imageBase64, int $fdid);
$faceService->deleteFace(int $fdid, int $fpid);
$faceService->searchFace(int $page, int $maxResults, string $faceLibType, ?int $fdid, ?string $fpid);
$faceService->deleteFaceSearch(int $fdid, string $faceLibType);
$faceService->uploadFaceDataRecord(int $fdid, string $fpid, string $imageContent, string $faceLibType);
$faceService->getLibraries();
$faceService->createLibrary(array $data);
$faceService->getCapabilities();

FingerprintService

$fingerprintService = app(FingerprintService::class);

$fingerprintService->add(string $employeeNo, int $fingerprintId, string $data);
$fingerprintService->search(...);
$fingerprintService->capture(int $timeout);
$fingerprintService->delete(array $employeeNos);
$fingerprintService->getCapabilities();

AccessControlService

$accessControl = app(AccessControlService::class);

$accessControl->openDoor(int $doorNo);
$accessControl->closeDoor(int $doorNo);
$accessControl->getDoorStatus(int $doorNo);

EventService

$eventService = app(EventService::class);

$eventService->search(array $conditions, int $page, int $maxResults);
$eventService->count(array $conditions);
$eventService->subscribe(array $eventTypes, int $heartbeat);

EventNotificationService

$eventNotificationService = app(EventNotificationService::class);

// Simple webhook setup
$eventNotificationService->configureWebhook(string $webhookUrl, int $hostId);

// Advanced configuration
$eventNotificationService->configureHttpHost(string $url, int $id, string $protocol, int $port, string $httpAuthType, ?string $username, ?string $password, array $eventTypes);

// Manage webhooks
$eventNotificationService->getHttpHost(int $id);
$eventNotificationService->getAllHttpHosts();
$eventNotificationService->enableHttpHost(int $id);
$eventNotificationService->disableHttpHost(int $id);
$eventNotificationService->removeHttpHost(int $id);

// Testing
$eventNotificationService->testHttpNotification(int $id);

// Capabilities
$eventNotificationService->getCapabilities();

DTOs (Data Transfer Objects)

Person DTO

use Shaykhnazar\HikvisionIsapi\DTOs\Person;
use Shaykhnazar\HikvisionIsapi\Enums\UserType;

$person = new Person(
    employeeNo: 'EMP001',
    name: 'John Doe',
    userType: UserType::NORMAL,
    validEnabled: true,
    beginTime: '2025-01-01T00:00:00',
    endTime: '2025-12-31T23:59:59',
    doorRight: '1',
    rightPlan: [
        ['doorNo' => 1, 'planTemplateNo' => '1']
    ],
    email: 'john@example.com',
    phoneNumber: '+1234567890',
    organizationId: 1,
    belongGroup: null
);

// Convert to array for API
$array = $person->toArray();

// Create from API response
$person = Person::fromArray($response);

Card DTO

use Shaykhnazar\HikvisionIsapi\DTOs\Card;

$card = new Card(
    employeeNo: 'EMP001',
    cardNo: '1234567890',
    cardType: 'normal',
    enabled: true
);

Face DTO

use Shaykhnazar\HikvisionIsapi\DTOs\Face;

$face = new Face(
    employeeNo: 'EMP001',
    faceData: base64_encode($imageData),
    faceLibId: 1,
    faceLibType: 'blackFD'
);

Enums

UserType

use Shaykhnazar\HikvisionIsapi\Enums\UserType;

UserType::NORMAL    // Normal user
UserType::VISITOR   // Visitor
UserType::BLOCKLIST // Blocklist user

// Get label
$label = UserType::NORMAL->label(); // "Normal User"

EventType

use Shaykhnazar\HikvisionIsapi\Enums\EventType;

EventType::ACCESS_GRANTED  // 0x01
EventType::ACCESS_DENIED   // 0x02
EventType::FACE_RECOGNIZED // 0x4b
EventType::CARD_SWIPED     // 0x05

// Get description
$desc = EventType::ACCESS_GRANTED->description(); // "Access Granted"

Error Handling

All exceptions extend HikvisionException:

use Shaykhnazar\HikvisionIsapi\Exceptions\HikvisionException;
use Shaykhnazar\HikvisionIsapi\Exceptions\AuthenticationException;
use Shaykhnazar\HikvisionIsapi\Exceptions\DeviceNotFoundException;

try {
    $response = $personService->add($person);
} catch (AuthenticationException $e) {
    // Handle authentication errors
    Log::error('Authentication failed', ['error' => $e->getMessage()]);
} catch (DeviceNotFoundException $e) {
    // Handle device not found
    Log::error('Device not found', ['error' => $e->getMessage()]);
} catch (HikvisionException $e) {
    // Handle other Hikvision errors
    Log::error('Hikvision error', ['error' => $e->getMessage()]);
}

Multi-Device Support

The package supports managing multiple Hikvision devices simultaneously. This is useful when you have multiple terminals at different locations (entrance, exit, canteen, etc.).

Device Sources Supported:

  • ✅ Config files (default)
  • ✅ Database tables
  • ✅ Custom callbacks (API, Redis, cache, etc.)
  • ✅ Runtime registration

Configuration from Config Files (Default)

Configure multiple devices in config/hikvision.php:

return [
    'default' => env('HIKVISION_DEFAULT_DEVICE', 'primary'),

    'devices' => [
        'primary' => [
            'ip' => env('HIKVISION_IP', '192.168.1.100'),
            'port' => env('HIKVISION_PORT', 80),
            'username' => env('HIKVISION_USERNAME', 'admin'),
            'password' => env('HIKVISION_PASSWORD'),
            'protocol' => env('HIKVISION_PROTOCOL', 'http'),
            'timeout' => env('HIKVISION_TIMEOUT', 30),
            'verify_ssl' => env('HIKVISION_VERIFY_SSL', false),
        ],

        'entrance' => [
            'ip' => env('HIKVISION_ENTRANCE_IP', '192.168.1.101'),
            'port' => env('HIKVISION_ENTRANCE_PORT', 80),
            'username' => env('HIKVISION_ENTRANCE_USERNAME', 'admin'),
            'password' => env('HIKVISION_ENTRANCE_PASSWORD'),
            'protocol' => env('HIKVISION_ENTRANCE_PROTOCOL', 'http'),
            'timeout' => env('HIKVISION_ENTRANCE_TIMEOUT', 30),
            'verify_ssl' => env('HIKVISION_ENTRANCE_VERIFY_SSL', false),
        ],

        'exit' => [
            'ip' => env('HIKVISION_EXIT_IP', '192.168.1.102'),
            'port' => env('HIKVISION_EXIT_PORT', 80),
            'username' => env('HIKVISION_EXIT_USERNAME', 'admin'),
            'password' => env('HIKVISION_EXIT_PASSWORD'),
            'protocol' => env('HIKVISION_EXIT_PROTOCOL', 'http'),
            'timeout' => env('HIKVISION_EXIT_TIMEOUT', 30),
            'verify_ssl' => env('HIKVISION_EXIT_VERIFY_SSL', false),
        ],

        'canteen' => [
            'ip' => env('HIKVISION_CANTEEN_IP', '192.168.1.103'),
            'port' => env('HIKVISION_CANTEEN_PORT', 80),
            'username' => env('HIKVISION_CANTEEN_USERNAME', 'admin'),
            'password' => env('HIKVISION_CANTEEN_PASSWORD'),
            'protocol' => env('HIKVISION_CANTEEN_PROTOCOL', 'http'),
            'timeout' => env('HIKVISION_CANTEEN_TIMEOUT', 30),
            'verify_ssl' => env('HIKVISION_CANTEEN_VERIFY_SSL', false),
        ],
    ],
];

Environment Variables

Add device-specific environment variables to .env:

# Primary Device (Default)
HIKVISION_DEFAULT_DEVICE=primary
HIKVISION_IP=192.168.1.100
HIKVISION_PORT=80
HIKVISION_USERNAME=admin
HIKVISION_PASSWORD=your_password

# Entrance Device
HIKVISION_ENTRANCE_IP=192.168.1.101
HIKVISION_ENTRANCE_PORT=80
HIKVISION_ENTRANCE_USERNAME=admin
HIKVISION_ENTRANCE_PASSWORD=entrance_password

# Exit Device
HIKVISION_EXIT_IP=192.168.1.102
HIKVISION_EXIT_PORT=80
HIKVISION_EXIT_USERNAME=admin
HIKVISION_EXIT_PASSWORD=exit_password

# Canteen Device
HIKVISION_CANTEEN_IP=192.168.1.103
HIKVISION_CANTEEN_PORT=80
HIKVISION_CANTEEN_USERNAME=admin
HIKVISION_CANTEEN_PASSWORD=canteen_password

Using Multiple Devices with Facade

use Shaykhnazar\HikvisionIsapi\Facades\Hikvision;

// Use default device
$defaultClient = Hikvision::default();
$info = $defaultClient->get('/ISAPI/System/deviceInfo');

// Use specific devices
$entranceClient = Hikvision::device('entrance');
$exitClient = Hikvision::device('exit');
$canteenClient = Hikvision::device('canteen');

// Get device information from each terminal
$entranceInfo = $entranceClient->get('/ISAPI/System/deviceInfo');
$exitInfo = $exitClient->get('/ISAPI/System/deviceInfo');
$canteenInfo = $canteenClient->get('/ISAPI/System/deviceInfo');

// List all available devices
$devices = Hikvision::availableDevices();
// Returns: ['primary', 'entrance', 'exit', 'canteen']

// Check if device exists
if (Hikvision::hasDevice('entrance')) {
    // Work with entrance device
}

Using Multiple Devices with Services

use Shaykhnazar\HikvisionIsapi\Facades\Hikvision;
use Shaykhnazar\HikvisionIsapi\Services\PersonService;
use Shaykhnazar\HikvisionIsapi\DTOs\Person;
use Shaykhnazar\HikvisionIsapi\Enums\UserType;

// Create person DTO
$person = new Person(
    employeeNo: 'EMP001',
    name: 'John Doe',
    userType: UserType::NORMAL,
    validEnabled: true,
    beginTime: now()->toISOString(),
    endTime: now()->addYear()->toISOString()
);

// Get device-specific clients
$entranceClient = Hikvision::device('entrance');
$exitClient = Hikvision::device('exit');
$canteenClient = Hikvision::device('canteen');

// Create person service for each device
$entrancePersonService = new PersonService($entranceClient);
$exitPersonService = new PersonService($exitClient);
$canteenPersonService = new PersonService($canteenClient);

// Add person to all devices
$entrancePersonService->add($person);
$exitPersonService->add($person);
$canteenPersonService->add($person);

Syncing Employee to Multiple Devices

use Shaykhnazar\HikvisionIsapi\Facades\Hikvision;
use Shaykhnazar\HikvisionIsapi\Services\PersonService;
use Shaykhnazar\HikvisionIsapi\Services\FaceService;
use Shaykhnazar\HikvisionIsapi\DTOs\Person;

class EmployeeSyncService
{
    public function syncToAllDevices(Person $person, string $faceImagePath): array
    {
        $results = [];
        $devices = Hikvision::availableDevices();

        foreach ($devices as $deviceName) {
            try {
                // Get device client
                $client = Hikvision::device($deviceName);

                // Create services for this device
                $personService = new PersonService($client);
                $faceService = new FaceService($client);

                // Add person
                $personService->add($person);

                // Upload face
                $imageData = file_get_contents($faceImagePath);
                $imageBase64 = base64_encode($imageData);
                $faceService->uploadFace($person->employeeNo, $imageBase64, 1);

                $results[$deviceName] = [
                    'success' => true,
                    'message' => 'Synced successfully',
                ];
            } catch (\Exception $e) {
                $results[$deviceName] = [
                    'success' => false,
                    'error' => $e->getMessage(),
                ];
            }
        }

        return $results;
    }
}

Device Manager Methods

The DeviceManager provides the following methods:

use Shaykhnazar\HikvisionIsapi\Facades\Hikvision;

// Get client for specific device (or default if null)
$client = Hikvision::device('entrance');
$client = Hikvision::device(); // Uses default device

// Get default device client
$defaultClient = Hikvision::default();

// Get all available device names
$devices = Hikvision::availableDevices();
// Returns: ['primary', 'entrance', 'exit', 'canteen']

// Check if device exists in configuration
$exists = Hikvision::hasDevice('entrance'); // true or false

// Reload devices from provider (useful for database-driven configs)
Hikvision::reload(); // Clears cache and reloads from source

// Clear cached clients (useful for testing)
Hikvision::clearClients();

// Register device at runtime (v1.3.0+)
Hikvision::registerDevice('temp_device', [
    'ip' => '192.168.1.150',
    'port' => 80,
    'username' => 'admin',
    'password' => 'password',
    'protocol' => 'http',
    'timeout' => 30,
    'verify_ssl' => false,
]);

// Switch device provider at runtime (v1.3.0+)
use Shaykhnazar\HikvisionIsapi\Client\Providers\DatabaseDeviceProvider;
$dbProvider = new DatabaseDeviceProvider(table: 'terminals');
Hikvision::setProvider($dbProvider);

Backward Compatibility

The package maintains 100% backward compatibility. If you're using the default device setup, your existing code will work without any changes:

use Shaykhnazar\HikvisionIsapi\Services\PersonService;

// This still works - uses default device
$personService = app(PersonService::class);
$person = $personService->add($newPerson);

Loading Devices from Database

For applications that store terminal configurations in database (multi-tenant, dynamic terminals, etc.), use the DatabaseDeviceProvider:

Step 1: Create Terminals Table

// Migration: create_terminals_table.php
Schema::create('terminals', function (Blueprint $table) {
    $table->id();
    $table->string('name')->unique(); // Device identifier
    $table->string('ip');
    $table->integer('port')->default(80);
    $table->string('username');
    $table->string('password');
    $table->string('protocol')->default('http');
    $table->integer('timeout')->default(30);
    $table->boolean('verify_ssl')->default(false);
    $table->boolean('is_active')->default(true);
    $table->timestamps();
});

Step 2: Register Database Provider

In your AppServiceProvider or custom service provider:

use Shaykhnazar\HikvisionIsapi\Client\Providers\DatabaseDeviceProvider;

public function register(): void
{
    // Bind custom device provider
    $this->app->singleton('hikvision.device.provider', function ($app) {
        return new DatabaseDeviceProvider(
            table: 'terminals',
            nameColumn: 'name',
            configColumns: [
                'ip' => 'ip',
                'port' => 'port',
                'username' => 'username',
                'password' => 'password',
                'protocol' => 'protocol',
                'timeout' => 'timeout',
                'verify_ssl' => 'verify_ssl',
            ],
            defaultDevice: 'primary',
            whereConditions: ['is_active' => true], // Only load active terminals
            cache: true, // Enable caching
            cacheTtl: 3600 // Cache for 1 hour
        );
    });
}

Step 3: Use Terminals from Database

use Shaykhnazar\HikvisionIsapi\Facades\Hikvision;
use Shaykhnazar\HikvisionIsapi\Services\PersonService;

// Get all available terminals from database
$terminals = Hikvision::availableDevices();
// Returns: ['entrance', 'exit', 'canteen', 'office'] - loaded from DB

// Use specific terminal
$entranceClient = Hikvision::device('entrance');
$personService = new PersonService($entranceClient);

// Add person to entrance terminal
$personService->add($person);

Step 4: Reload Devices When Database Changes

use Shaykhnazar\HikvisionIsapi\Facades\Hikvision;

// After adding/updating terminals in database
Hikvision::reload(); // Clears cache and reloads from DB

Using Eloquent Models with CallbackProvider

For more complex scenarios with Eloquent relationships:

use Shaykhnazar\HikvisionIsapi\Client\Providers\CallbackDeviceProvider;
use App\Models\Terminal;

// In your service provider
$this->app->singleton('hikvision.device.provider', function ($app) {
    return CallbackDeviceProvider::fromEloquent(
        query: Terminal::where('status', 'active')
                      ->where('company_id', auth()->user()->company_id),
        nameColumn: 'slug',
        configMap: [
            'ip' => 'ip_address',
            'port' => 'port',
            'username' => 'username',
            'password' => 'password',
            'protocol' => 'protocol',
            'timeout' => 'connection_timeout',
            'verify_ssl' => 'ssl_enabled',
        ]
    );
});

Multi-Tenant Support Example

For multi-tenant applications where each tenant has their own terminals:

use Shaykhnazar\HikvisionIsapi\Client\Providers\CallbackDeviceProvider;
use App\Models\Terminal;

// In AppServiceProvider
$this->app->singleton('hikvision.device.provider', function ($app) {
    return new CallbackDeviceProvider(
        deviceNamesCallback: function () {
            // Only load terminals for current tenant
            $tenantId = auth()->user()->tenant_id;
            return Terminal::where('tenant_id', $tenantId)
                          ->where('is_active', true)
                          ->pluck('name')
                          ->toArray();
        },
        deviceConfigCallback: function (string $deviceName) {
            $tenantId = auth()->user()->tenant_id;
            $terminal = Terminal::where('tenant_id', $tenantId)
                               ->where('name', $deviceName)
                               ->first();

            if (!$terminal) {
                return null;
            }

            return [
                'ip' => $terminal->ip,
                'port' => $terminal->port,
                'username' => $terminal->username,
                'password' => decrypt($terminal->password), // Decrypt if encrypted
                'protocol' => $terminal->protocol,
                'timeout' => $terminal->timeout,
                'verify_ssl' => $terminal->verify_ssl,
            ];
        },
        defaultDevice: 'primary'
    );
});

Runtime Device Registration

Register devices dynamically at runtime:

use Shaykhnazar\HikvisionIsapi\Facades\Hikvision;

// Register a temporary device
Hikvision::registerDevice('temp_device', [
    'ip' => '192.168.1.150',
    'port' => 80,
    'username' => 'admin',
    'password' => 'password',
    'protocol' => 'http',
    'timeout' => 30,
    'verify_ssl' => false,
]);

// Use the temporary device
$client = Hikvision::device('temp_device');

Switching Providers at Runtime

Change device provider dynamically:

use Shaykhnazar\HikvisionIsapi\Facades\Hikvision;
use Shaykhnazar\HikvisionIsapi\Client\Providers\DatabaseDeviceProvider;
use Shaykhnazar\HikvisionIsapi\Client\Providers\ConfigDeviceProvider;

// Switch to database provider
$dbProvider = new DatabaseDeviceProvider(table: 'terminals');
Hikvision::setProvider($dbProvider);

// Now all devices are loaded from database
$devices = Hikvision::availableDevices();

// Switch back to config provider
$configProvider = new ConfigDeviceProvider(config('hikvision'));
Hikvision::setProvider($configProvider);

Advanced Usage

Batch Operations

$cardService = app(CardService::class);

$cards = [
    new Card('EMP001', '1234567890'),
    new Card('EMP002', '0987654321'),
    new Card('EMP003', '1122334455'),
];

$results = $cardService->batchAdd($cards);

/*
Returns:
[
    'total' => 3,
    'success' => 3,
    'failed' => 0,
    'errors' => []
]
*/

Pagination

$personService = app(PersonService::class);
$total = $personService->count();
$perPage = 30;
$pages = ceil($total / $perPage);

for ($page = 0; $page < $pages; $page++) {
    $persons = $personService->search($page, $perPage);
    // Process persons...
}

Custom HTTP Requests

use Shaykhnazar\HikvisionIsapi\Facades\HikvisionIsapi;

// GET request
$response = HikvisionIsapi::get('/ISAPI/CustomEndpoint', [
    'param1' => 'value1',
]);

// POST request
$response = HikvisionIsapi::post('/ISAPI/CustomEndpoint', [
    'key' => 'value',
]);

// PUT request
$response = HikvisionIsapi::put('/ISAPI/CustomEndpoint', [
    'key' => 'value',
]);

// DELETE request
$response = HikvisionIsapi::delete('/ISAPI/CustomEndpoint');

Testing

# Run tests
./vendor/bin/phpunit

# Run specific test file
./vendor/bin/phpunit tests/Feature/PersonServiceTest.php

# Run with coverage
./vendor/bin/phpunit --coverage-html coverage

Troubleshooting

Authentication Failed (401)

Check your credentials in .env:

# Test with curl
curl -v --digest -u admin:password http://192.168.1.100/ISAPI/System/deviceInfo

Connection Timeout

  • Verify device IP and network connectivity
  • Increase timeout in config: HIKVISION_TIMEOUT=60
  • Check firewall rules

Face Upload Fails

  • Ensure image is JPEG format
  • Check image size (max 200KB)
  • Verify person exists on device first
  • Image quality should be good with clear face

Device Not Found

  • Check IP address in .env
  • Verify device is powered on
  • Ensure device is on same network

Security

See SECURITY.md for supported versions, vulnerability reporting instructions, and disclosure policy. Do not open public GitHub issues for suspected vulnerabilities; report them privately through the channels described there.

  • Store Hikvision device and webhook credentials in environment variables or a secret manager; never commit them
  • Prefer HTTPS/TLS for device and webhook communication where supported
  • Enable SSL certificate verification with valid certificates in production
  • Restrict device access with firewalls, VPNs, or private networks
  • Implement rate limiting on your API endpoints
  • Validate webhook payloads and all user-controlled input
  • Treat XML input as untrusted and parse it defensively
  • Avoid logging credentials, tokens, face images, biometric data, or raw personally identifiable data
  • Rotate device and webhook credentials after suspected compromise

Contributing

Contributions are welcome! Please follow these guidelines:

  1. Fork the repository
  2. Create a feature branch
  3. Write tests for new features
  4. Follow PSR-12 coding standards
  5. Submit a pull request

Changelog

For full release history, see CHANGELOG.md and the GitHub releases/tags.

v1.5.4 (2025-11-05)

  • Current stable release on Packagist.
  • Fixed FaceService image filenames so supported face upload/update flows use unique {fpid}.jpg filenames.

v1.4.0 (2025-10-30)

  • EventNotificationService: New service for configuring HTTP event notifications (webhooks)
  • Real-Time Webhooks: Configure devices to push events to your API endpoints automatically
  • XML Support: Added putXml() method to HikvisionClient for XML-only endpoints
  • HTTP Client Enhancements:
    • Added arrayToXml() method for converting arrays to XML format
    • Added xmlToArray() method for parsing XML responses
    • Automatic XML/JSON format detection based on Content-Type
  • Event Notification Methods:
    • configureWebhook() - Simple one-line webhook setup
    • configureHttpHost() - Advanced webhook configuration with authentication
    • enableHttpHost() / disableHttpHost() - Enable/disable webhooks
    • testHttpNotification() - Test webhook connectivity
    • getCapabilities() - Get notification capabilities
  • Dependencies: Added ext-simplexml and ext-libxml for XML processing
  • Multi-Device Webhooks: Configure webhooks for multiple devices simultaneously
  • Webhook Security: Support for HTTP Basic and Digest authentication
  • 100% backward compatible with v1.3.0

v1.3.0 (2025-10-13)

  • Universal Device Providers: Framework for loading devices from any source
  • DatabaseDeviceProvider: Load terminals from database tables with built-in caching
  • CallbackDeviceProvider: Maximum flexibility with custom callbacks (API, Redis, etc.)
  • Multi-Tenant Support: Tenant-scoped device loading and isolation
  • Runtime Device Registration: Register devices dynamically at runtime
  • Provider Switching: Change device providers on-the-fly
  • Hot Reload: Update database-driven configurations without restart
  • Configurable Caching: Built-in cache support with TTL control
  • 100% backward compatible with v1.2.0

v1.2.0 (2025-10-13)

  • Multi-Device Support: Manage unlimited Hikvision devices simultaneously
  • DeviceManager: Central manager with lazy initialization and caching
  • New Hikvision Facade: Multi-device access with Hikvision::device()
  • Device Discovery: List and validate configured devices
  • Device-Specific Clients: Automatic client caching per device
  • 100% backward compatible with single-device setups

v1.1.0 (2025-10-13)

  • Face Search Functionality: Search face data with pagination support
  • Face Data Record Upload: Multipart/form-data support for face images
  • Fixed PersonService Delete Endpoint: Updated to match ISAPI specification
  • Configuration Validation: Required username/password validation
  • Extended HTTP Client: Added multipart form-data upload support
  • Enhanced error handling and validation

v1.0.0 (2025-10-09)

  • Initial release
  • Full ISAPI support
  • Person, Card, Face, Fingerprint management
  • Access control and event handling
  • Laravel 11/12/13 support
  • PHP 8.2+ with modern features

License

This package is open-sourced software licensed under the MIT license.

Credits

Support

For issues, questions, or contributions:

Disclaimer

This package is designed for legitimate access control systems and defensive security purposes only. Do not use for malicious purposes.

Made with ❤️ for the Laravel community