_ _ _ _ ____ _
| | | | | | | | | _ \ | |
| | | | __ _| |_ ___| |__ | |_) | ___ ___| |_
| |/\| |/ _` | __/ __| '_ \| __/ / _ \/ __| __|
\ /\ / (_| | || (__| | | || | | (_) \__ \ |_
\/ \/ \__,_|\__\___|_| |_||_| \___/|___/\__|
Intelligent venue security for UniFi Protect deployments
WatchPost is a self-hosted security intelligence platform that sits on top of UniFi Protect camera systems. It adds watchlist management, AI-powered face detection, license plate recognition, real-time alerts, and audit logging — all running locally with zero cloud dependency.
Built for maritime venues, co-working spaces, event venues, and marinas.
Screenshots coming soon. WatchPost features a dark-mode dashboard with real-time detection feeds, watchlist management, and camera overview.
- Watchlist Management — Ban lists, watch lists, and VIP lists with expiration and notes
- AI Face Detection — InsightFace buffalo_sc model for 512-dimensional face embeddings
- Face Enrollment — Upload reference photos to enroll subjects for automated matching
- Real-time Matching — pgvector cosine similarity search against enrolled faces
- Live Detection Feed — WebSocket-powered real-time event stream in the dashboard
- Multi-channel Alerts — Webhook, SMS (Twilio), and email (SendGrid) notifications
- Camera Sync — Auto-import cameras from UniFi Protect controller
- Event Review — Confirm or dismiss detection events with full audit trail
- Audit Logging — Every action logged with user, timestamp, and IP address
- Role-based Access — Admin, operator, and viewer roles
- Docker Deployment — Single
docker compose upto run everything - 100% Local — No cloud services required, all data stays on-premises
- License plate recognition (LPR)
- Multi-site management
- Mobile push notifications
- Tailgating detection
- Occupancy counting
- Report generation and export
┌──────────────────────────────────────────┐
│ Docker Compose │
│ │
┌────────┐ │ ┌─────────┐ ┌──────────────────┐ │
│ UniFi │◄──wss──┤ │ Worker │────►│ Face Sidecar │ │
│Protect │ │ │ (Node) │ │ (Python/ONNX) │ │
└────────┘ │ └────┬────┘ └──────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────┐ ┌──────────────────┐ │
┌────────┐ │ │ API │────►│ PostgreSQL │ │
│Browser │◄──ws───┤ │(Fastify)│ │ + pgvector │ │
│ /App │◄──http─┤ └────┬────┘ └──────────────────┘ │
└────────┘ │ │ │
│ ▼ │
│ ┌─────────┐ ┌──────────────────┐ │
│ │ Web │ │ Redis │ │
│ │(Next.js)│ │ (pub/sub) │ │
│ └─────────┘ └──────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ MinIO (S3 storage) │ │
│ └──────────────────────────────────┘ │
└──────────────────────────────────────────┘
- Worker connects to UniFi Protect via WebSocket and subscribes to motion and smart detection events
- Protect uses a binary protocol: 8-byte header (action frame) + variable-length JSON body
- A second optional binary frame carries the snapshot payload
- On detection, it fetches the camera snapshot via
apiRequestRaw()and sends it to the Face Sidecar - Face Sidecar runs InsightFace to extract 512-d face embeddings
- Worker queries pgvector for the nearest enrolled face
- If a match is found (cosine distance < threshold), an alert is created
- The event is published to Redis and broadcast to dashboard clients via WebSocket
- Snapshots are stored in MinIO for later review
- Docker and Docker Compose v2
- UniFi Protect controller with API access
- A machine with at least 4GB RAM (8GB recommended)
# 1. Clone the repository
git clone https://github.com/tjcrowley/watchpost.git
cd watchpost
# 2. Copy environment config
cp .env.example .env
# 3. Edit .env with your Protect credentials
# At minimum, set:
# - PROTECT_URL
# - PROTECT_USERNAME
# - PROTECT_PASSWORD
# - JWT_SECRET (generate with: openssl rand -hex 32)
# 4. Start all services
docker compose -f infra/docker-compose.yml up -d
# 5. Open the dashboard
open http://localhost:3000Or use the one-command installer:
bash scripts/install.sh| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
postgresql://watchpost:watchpost@postgres:5432/watchpost |
PostgreSQL connection string |
REDIS_URL |
redis://redis:6379 |
Redis connection string |
MINIO_ENDPOINT |
minio |
MinIO hostname |
MINIO_PORT |
9000 |
MinIO API port |
MINIO_ACCESS_KEY |
watchpost |
MinIO access key |
MINIO_SECRET_KEY |
— | MinIO secret key |
MINIO_BUCKET |
watchpost |
MinIO bucket name |
JWT_SECRET |
— | Required. Secret for JWT signing |
PROTECT_URL |
— | Required. UniFi Protect controller URL |
PROTECT_USERNAME |
— | Required. Protect service account username |
PROTECT_PASSWORD |
— | Required. Protect service account password |
FACE_SIDECAR_URL |
http://face-sidecar:5500 |
Face detection service URL |
MATCH_THRESHOLD |
0.4 |
Cosine distance threshold (lower = stricter) |
ALERT_WEBHOOK_URL |
— | Webhook URL for alerts (optional) |
TWILIO_ACCOUNT_SID |
— | Twilio account SID for SMS (optional) |
TWILIO_AUTH_TOKEN |
— | Twilio auth token (optional) |
TWILIO_FROM |
— | Twilio sender phone number (optional) |
SENDGRID_API_KEY |
— | SendGrid API key for email (optional) |
FEATURE_LPR |
false |
Enable license plate recognition |
FEATURE_MULTISITE |
false |
Enable multi-site management |
- Intel N100 mini PC (or equivalent ARM64)
- 8GB RAM minimum
- 256GB SSD (plus external storage for snapshots)
- Ubuntu 22.04 LTS or Debian 12
- UniFi G4 Pro / G4 Bullet / G4 Dome
- UniFi G4 Instant
- UniFi G3 Flex / G3 Instant / G3 Bullet
- Any camera managed by UniFi Protect 4.x+
- Face detection: ~50ms per frame on N100 (CPU/ONNX)
- pgvector matching: <5ms for 10,000 enrolled faces
- End-to-end detection-to-alert: <500ms typical
- Navigate to Watchlist in the dashboard
- Click Add Subject
- Fill in name, list type (Ban/Watch/VIP), and optional reason
- Click Add
- Find the subject in the watchlist table
- Click Enroll Face
- Upload a clear photo of the person's face
- The system extracts a 512-dimensional embedding and stores it
- Multiple photos can be enrolled per subject for better accuracy
| Type | Purpose | Alert Behavior |
|---|---|---|
| Ban | Prohibited individuals | Immediate alert on all channels |
| Watch | Persons of interest | Alert on configured channels |
| VIP | Welcome guests | No alert (logged for records) |
Set ALERT_WEBHOOK_URL in your .env file. WatchPost sends a POST request with JSON payload:
{
"source": "watchpost",
"event_id": "uuid",
"event_type": "smartDetectZone",
"camera_id": "uuid",
"detected_at": "2025-01-15T10:30:00Z",
"subject_name": "John Doe",
"list_type": "ban",
"confidence": 0.92,
"sent_at": "2025-01-15T10:30:00Z"
}- Set
TWILIO_ACCOUNT_SID,TWILIO_AUTH_TOKEN, andTWILIO_FROMin.env - Configure recipient numbers in the settings (coming in v1.1)
- Set
SENDGRID_API_KEYin.env - Configure recipient emails in the settings (coming in v1.1)
Enable with FEATURE_MULTISITE=true. Each site has:
- Its own UniFi Protect controller connection
- Separate camera and subject databases
- Independent alert configurations
- Shared user accounts with per-site role assignments
All endpoints require JWT authentication (except /api/auth/login and /health).
| Method | Path | Description |
|---|---|---|
POST |
/api/auth/login |
Login with email/password, returns JWT |
POST |
/api/auth/logout |
Logout (client discards token) |
GET |
/api/auth/me |
Get current user info |
| Method | Path | Description |
|---|---|---|
GET |
/api/watchlist |
List subjects (paginated) |
GET |
/api/watchlist/:id |
Get subject by ID |
POST |
/api/watchlist |
Create subject |
PATCH |
/api/watchlist/:id |
Update subject |
DELETE |
/api/watchlist/:id |
Delete subject |
POST |
/api/watchlist/:id/enroll |
Enroll face photo |
| Method | Path | Description |
|---|---|---|
GET |
/api/events |
List events (paginated, filterable) |
GET |
/api/events/:id |
Get event details |
PATCH |
/api/events/:id/review |
Review event (confirm/dismiss) |
| Method | Path | Description |
|---|---|---|
GET |
/api/cameras |
List cameras |
GET |
/api/cameras/:id |
Get camera details |
PATCH |
/api/cameras/:id |
Update camera config |
POST |
/api/cameras/sync |
Sync cameras from Protect |
GET |
/api/cameras/:id/snapshot |
Proxy live snapshot from Protect |
| Method | Path | Description |
|---|---|---|
GET |
/api/alerts |
List alerts (paginated) |
GET |
/api/alerts/:id |
Get alert details |
| Path | Description |
|---|---|
ws://host:3001/api/ws |
Real-time event stream |
# Install pnpm if needed
npm install -g pnpm
# Install dependencies
pnpm install
# Start infrastructure (Postgres, Redis, MinIO)
docker compose -f infra/docker-compose.yml up -d postgres redis minio
# Run all services in dev mode
pnpm dev
# Or run individually:
cd apps/api && pnpm dev # API on :3001
cd apps/web && pnpm dev # Web on :3000
cd apps/worker && pnpm dev # Worker
# Face sidecar (Python)
cd apps/face-sidecar
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
uvicorn main:app --host 0.0.0.0 --port 5500 --reloadwatchpost/
├── apps/
│ ├── api/ # Fastify REST API + WebSocket
│ ├── web/ # Next.js 15 dashboard
│ ├── worker/ # Protect connector + detection pipeline
│ └── face-sidecar/ # Python FastAPI + InsightFace
├── packages/
│ ├── types/ # Shared TypeScript types
│ ├── db/ # Database client + pg-boss queue
│ └── logger/ # Pino logger wrapper
├── infra/
│ ├── docker-compose.yml
│ ├── migrations/ # PostgreSQL schema
│ └── nginx.conf # Reverse proxy config
└── scripts/
└── install.sh # One-command installer
- Watchlist management (ban/watch/VIP)
- Face detection via InsightFace
- pgvector face matching
- Real-time WebSocket events
- Multi-channel alerts (webhook, SMS, email)
- Audit logging
- Docker Compose deployment
- UniFi Protect binary WebSocket protocol (8-byte header framing)
- Motion + smartDetect event detection
- Camera snapshot proxy (
GET /api/cameras/:id/snapshot) - All containers healthy (health checks fixed for face-sidecar and nginx)
- Alert destination management UI
- User management UI
- Detection event image viewer
- Dashboard analytics widgets
- Configurable match threshold per list type
- License plate recognition (LPR)
- Multi-site management
- Mobile push notifications (via Pushover/ntfy)
- Tailgating detection
- Occupancy counting
- Scheduled reports
- SSO/SAML authentication
- Multi-tenant architecture
- API rate limiting and API keys
- Prometheus metrics and Grafana dashboards
- Kubernetes Helm chart
- Encrypted at-rest storage
MIT License. See LICENSE for details.
- Turborepo — Monorepo build system
- Fastify — Node.js API framework
- Next.js 15 — React framework
- Tailwind CSS — Utility-first CSS
- InsightFace — Face detection & recognition
- pgvector — Vector similarity for PostgreSQL
- pg-boss — Job queue for PostgreSQL
- Redis — Pub/sub and caching
- MinIO — S3-compatible object storage
- UniFi Protect — Camera platform