The moment a license plate is caught on camera, the gate opens. No human involvement.
That is the outcome of Agentic AI parking automation. And what makes it possible is not complex AI infrastructure. A single NVR WebHook is enough.
This is a hands-on guide to building, from scratch, a system that uses NOX NVR WebHooks to receive LPR (license plate recognition) events, control the parking gate automatically, and record parking data.
Architecture Overview
The end-to-end flow of the parking automation system is as follows:
[Camera] → [NOX NVR: LPR detection]
↓ HTTP POST WebHook
[WebHook server: receive event]
↓
[Whitelist DB lookup] → Registered vehicle?
↓ YES
[Gate API call] → Open gate
↓
[Parking DB record] → Entry/exit time, plate, camera ID
↓
[Send notification] → Slack / messenger
Total processing time: within 1–2 seconds of plate recognition. NOX NVR fires the WebHook the instant it detects an event, and developers are free to build whatever logic follows.
Configuring the NOX NVR WebHook
1. Register the WebHook URL
In the NOX NVR admin console, go to Event Settings → WebHook Configuration and register your external server URL.
POST http://your-server.com/webhook/nvr/events
Content-Type: application/json
2. WebHook Payload Structure
The payload NOX NVR sends when an LPR event occurs:
{
"event_type": "lpr_detected",
"timestamp": "2026-04-21T09:15:32.418Z",
"camera_id": "CAM_ENTRANCE_01",
"camera_name": "Entrance Camera",
"channel": 1,
"data": {
"license_plate": "12가3456",
"confidence": 0.97,
"direction": "in",
"image_url": "http://nvr-host/snapshots/lpr/20260421_091532_CAM01.jpg",
"crop_image_url": "http://nvr-host/snapshots/lpr/crop/20260421_091532_CAM01.jpg"
},
"device_id": "NOX-NVR-SN-20240001",
"site_id": "SITE_PARKLOT_MAIN"
}
Key fields:
event_type: Event type (lpr_detected,motion_detected,line_crossing, etc.)data.license_plate: Recognized license platedata.confidence: Recognition confidence (0–1; 0.85 or higher recommended)data.direction: Entry (in) / exit (out)data.image_url: Full snapshot image URL
Implementation: The WebHook Server
Example Implementation in Python (FastAPI)
from fastapi import FastAPI, Request, HTTPException
from datetime import datetime
import httpx
import sqlite3
import asyncio
app = FastAPI()
# Whitelist DB (use a dedicated database in production)
WHITELIST_DB = "parking.db"
GATE_API_URL = "http://gate-controller.local/api/v1"
SLACK_WEBHOOK = "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"
@app.post("/webhook/nvr/events")
async def handle_nvr_event(request: Request):
payload = await request.json()
# Handle LPR events only
if payload.get("event_type") != "lpr_detected":
return {"status": "ignored"}
plate = payload["data"]["license_plate"]
confidence = payload["data"]["confidence"]
direction = payload["data"]["direction"]
camera_id = payload["camera_id"]
# Confidence filter (ignore below 85%)
if confidence < 0.85:
return {"status": "low_confidence", "plate": plate}
# Whitelist lookup
is_registered = check_whitelist(plate)
if is_registered:
# Gate control + DB record + notification in parallel
await asyncio.gather(
open_gate(direction, camera_id),
record_parking_event(plate, direction, camera_id, payload["timestamp"]),
send_slack_notification(plate, direction, camera_id)
)
return {"status": "access_granted", "plate": plate}
else:
# Record and alert on unregistered vehicle
await record_unknown_vehicle(plate, camera_id, payload["timestamp"])
return {"status": "access_denied", "plate": plate}
def check_whitelist(plate: str) -> bool:
conn = sqlite3.connect(WHITELIST_DB)
cursor = conn.cursor()
cursor.execute(
"SELECT id FROM whitelist WHERE license_plate = ? AND is_active = 1",
(plate,)
)
result = cursor.fetchone()
conn.close()
return result is not None
async def open_gate(direction: str, camera_id: str):
"""Call the gate API — controls actual hardware"""
gate_id = get_gate_id_from_camera(camera_id)
async with httpx.AsyncClient(timeout=3.0) as client:
await client.post(
f"{GATE_API_URL}/gates/{gate_id}/open",
json={"duration_seconds": 10, "reason": "lpr_access"}
)
async def record_parking_event(plate: str, direction: str, camera_id: str, timestamp: str):
"""Record parking event in DB"""
conn = sqlite3.connect(WHITELIST_DB)
cursor = conn.cursor()
cursor.execute(
"""INSERT INTO parking_log
(license_plate, direction, camera_id, event_time, created_at)
VALUES (?, ?, ?, ?, ?)""",
(plate, direction, camera_id, timestamp, datetime.utcnow().isoformat())
)
conn.commit()
conn.close()
async def send_slack_notification(plate: str, direction: str, camera_id: str):
"""Send Slack notification"""
direction_label = "Entry" if direction == "in" else "Exit"
message = f"🚗 [{direction_label}] {plate} — {camera_id} ({datetime.now().strftime('%H:%M:%S')})"
async with httpx.AsyncClient() as client:
await client.post(SLACK_WEBHOOK, json={"text": message})
def get_gate_id_from_camera(camera_id: str) -> str:
"""Map camera ID → gate ID"""
mapping = {
"CAM_ENTRANCE_01": "GATE_01",
"CAM_ENTRANCE_02": "GATE_02",
"CAM_EXIT_01": "GATE_03",
}
return mapping.get(camera_id, "GATE_DEFAULT")
Example Implementation in Node.js (Express)
const express = require('express');
const axios = require('axios');
const Database = require('better-sqlite3');
const app = express();
app.use(express.json());
const db = new Database('parking.db');
const GATE_API_URL = 'http://gate-controller.local/api/v1';
app.post('/webhook/nvr/events', async (req, res) => {
const { event_type, data, camera_id, timestamp } = req.body;
if (event_type !== 'lpr_detected') {
return res.json({ status: 'ignored' });
}
const { license_plate: plate, confidence, direction } = data;
if (confidence < 0.85) {
return res.json({ status: 'low_confidence', plate });
}
const isRegistered = checkWhitelist(plate);
if (isRegistered) {
await Promise.all([
openGate(direction, camera_id),
recordParkingEvent(plate, direction, camera_id, timestamp),
]);
return res.json({ status: 'access_granted', plate });
} else {
return res.json({ status: 'access_denied', plate });
}
});
function checkWhitelist(plate) {
const row = db.prepare(
'SELECT id FROM whitelist WHERE license_plate = ? AND is_active = 1'
).get(plate);
return !!row;
}
async function openGate(direction, cameraId) {
const gateId = getGateId(cameraId);
await axios.post(`${GATE_API_URL}/gates/${gateId}/open`, {
duration_seconds: 10,
reason: 'lpr_access',
});
}
function recordParkingEvent(plate, direction, cameraId, timestamp) {
db.prepare(
`INSERT INTO parking_log (license_plate, direction, camera_id, event_time)
VALUES (?, ?, ?, ?)`
).run(plate, direction, cameraId, timestamp);
}
function getGateId(cameraId) {
const mapping = {
CAM_ENTRANCE_01: 'GATE_01',
CAM_EXIT_01: 'GATE_02',
};
return mapping[cameraId] || 'GATE_DEFAULT';
}
app.listen(8080, () => console.log('WebHook server running on :8080'));
DB Schema
-- Whitelist (registered vehicles)
CREATE TABLE whitelist (
id INTEGER PRIMARY KEY AUTOINCREMENT,
license_plate TEXT NOT NULL UNIQUE,
owner_name TEXT,
vehicle_type TEXT DEFAULT 'car',
is_active INTEGER DEFAULT 1,
expires_at TEXT,
created_at TEXT DEFAULT (datetime('now'))
);
-- Entry/exit log
CREATE TABLE parking_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
license_plate TEXT NOT NULL,
direction TEXT NOT NULL, -- 'in' or 'out'
camera_id TEXT NOT NULL,
event_time TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
);
-- Unregistered vehicle log
CREATE TABLE unknown_vehicles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
license_plate TEXT NOT NULL,
camera_id TEXT NOT NULL,
event_time TEXT NOT NULL,
image_url TEXT,
created_at TEXT DEFAULT (datetime('now'))
);
Using the NOX NVR REST API
If WebHooks push events in the NVR → external server direction, the REST API provides control in the external server → NVR direction. Used together, they enable full two-way automation.
List Cameras
GET /api/v1/cameras
Authorization: Bearer {api_token}
Sample response:
{
"cameras": [
{
"id": "CAM_ENTRANCE_01",
"name": "Entrance Camera",
"channel": 1,
"status": "online",
"resolution": "1920x1080",
"ai_enabled": true,
"ai_models": ["lpr", "motion"]
}
]
}
Request a Live Snapshot
If you need an additional image when a particular event occurs, you can request a snapshot immediately via the REST API:
GET /api/v1/cameras/CAM_ENTRANCE_01/snapshot
Authorization: Bearer {api_token}
Query Event History
GET /api/v1/events?event_type=lpr_detected&from=2026-04-21T00:00:00Z&limit=100
Authorization: Bearer {api_token}
Operational Tips
1. Handling WebHook Retries
WebHook delivery can fail due to network issues. Design your handler to be idempotent:
@app.post("/webhook/nvr/events")
async def handle_nvr_event(request: Request):
payload = await request.json()
# Prevent duplicate processing: dedup by event_id
event_id = f"{payload['device_id']}_{payload['timestamp']}_{payload['data']['license_plate']}"
if is_already_processed(event_id):
return {"status": "duplicate", "event_id": event_id}
mark_processed(event_id)
# ... continue processing
2. Handling Gate API Timeouts
If the gate control API call fails, log it and send an alert for manual handling. Return the WebHook response to the NVR immediately and process gate control asynchronously to avoid WebHook timeouts.
@app.post("/webhook/nvr/events")
async def handle_nvr_event(request: Request):
payload = await request.json()
# Return immediately (prevents timeouts on the NVR side)
asyncio.create_task(process_event(payload))
return {"status": "received"}
3. Tuning the Confidence Threshold
The confidence value varies with camera image quality, lighting, and plate condition. Start at 0.85 and adjust based on real operating data. For night-time operation, you may consider lowering it to 0.80.
Summary
The core of Agentic AI parking automation with NOX NVR WebHooks is simple:
| Component | Role |
|---|---|
| NOX NVR + LPR | Detects plates and sends WebHooks |
| WebHook server | Receives events, runs business logic |
| Whitelist DB | Manages registered vehicles |
| Gate API | Controls hardware |
| Parking DB | Records entries and exits |
With fewer than 100 lines of code, you can connect plate recognition all the way to automatic gate control. If your existing systems already expose a REST API, integration costs drop even further.
Agentic AI is not a grandiose concept. It is implementing a detect → decide → act loop in code. NOX NVR provides the starting point of that loop.
👉 See a live demo in the NOX Vision AI Showroom — watch how a WebHook automation system works in practice
Technical inquiries and POC requests for NOX NVR WebHook integration: yiyolcorp.github.io
Related Posts
- What Is an Agentic AI NVR? The Decisive Difference from an AI NVR — The three generations of video surveillance AI, up to LLM-based detection of unstructured events
- Why NVR Hardware Makers Need a Software Partner — A partnership strategy for entering the Agentic AI NVR market without in-house development