I built OpenALPR-Webhook because I wanted more control over the records from my license-plate recognition cameras. At the time, Rekor Scout's hosted service kept records for two weeks and limited its blacklist to five plates. I wanted to keep records longer, maintain a larger blacklist, and search the data through my own web interface.

The application receives data from Rekor Scout through webhooks: messages sent to my application when Scout has a new record or alert. It saves those records locally and provides a dashboard for reviewing captures, searching plates, and managing alerts.

I deployed it with five rebranded Dahua IP cameras and two Rekor Scout agents across two locations, one of them remote. This was my first web application. It brought together a lot of features, and it also taught me where I needed to separate parts of the application more carefully.

What I built

I wrote the application in Python using Flask, with SQLAlchemy for database access and Alembic for managing database changes. Redis and RQ handled background jobs, such as downloading images and sending notifications. The application ran on Linux.

It receives three kinds of records: grouped plate reads, alerts, and vehicle records where no plate was recognized. For each incoming message, it checks the data format and required fields, checks the API key when authorization is enabled, saves the record, and refreshes the dashboard's cached data.

The web interface includes capture and plate searches, custom alerts, alerts received from Rekor, user accounts and roles, notification settings, and report branding. It also has maintenance views for checking queued jobs and background workers.

The camera integration goes beyond receiving records. The application can find configured Rekor Scout agents and download higher-resolution capture images from them. It also supports scheduled focus and zoom adjustments for supported Dahua cameras.

How incoming records are handled

  1. Rekor Scout sends a record or alert to the application.
  2. The application checks the message format and, if enabled, its authorization.
  3. It saves the record locally and updates the cached data used by the dashboard and search pages.
  4. Background workers download images, send notifications, and handle camera-control tasks.
  5. Users can search the saved records, configure alerts, and check worker activity through the web interface.

I kept slower tasks in the background so the sender would not have to wait for an image download or notification before getting a response.

Controlling access to the data

The application includes per-user API tokens, user-role checks, account controls, and IP blocking. Authorization for incoming webhook messages is configurable. It also has a mode that accepts messages without authentication, which would let an untrusted sender add records. I would not use that mode in a real deployment.

License-plate records also need careful handling. Images, timestamps, and camera locations can reveal more together than any one record does on its own. In a new version, I would define who can access the records, how long to keep them, how access is logged, and how backups are protected.

Design review: what I would change now

Run background workers separately from the web application

The original application starts a local Worker Manager Server, which creates Redis/RQ workers for enabled agents and cameras. That made worker management depend too closely on the web application. The repository documents conflicts when running multiple Gunicorn web workers. The worker manager also clears Redis state during startup and shutdown.

I would run the web application, background workers, and Redis as separate services, each managed independently. Restarting the web application should not clear the state used by the workers.

I would package the application in Docker and use Docker Compose to set up the services consistently during development. I would also move from the embedded local database to PostgreSQL to better support simultaneous database access and backup management. Health checks, retries, a way to hold failed jobs for inspection, and a tested backup-and-restore process would be part of that work.

Build the interface more consistently

I built the original interface with server-rendered templates and a purchased theme. It gave users a working interface, but the appearance and behavior did not fit together as well as I wanted.

Today I would use a framework such as NiceGUI to build the screens and their behavior together in Python. I would give forms, validation, sign-in behavior, and user actions a consistent approach instead of spreading that work across templates and theme code.

Update dependencies and access controls

The application uses older dependencies and a custom API key included in webhook data. In a new version, I would update those dependencies and check them automatically for known vulnerabilities.

I would also review how incoming messages prove who sent them, using signed requests or credentials with limited permissions where the sender supports them. Request limits, logs of security-relevant actions, protected credential storage, and tests for each authorization setting would be part of the redesign.

Test what happens when something fails

I would add repeatable tests for valid and malformed messages, rejected credentials, worker restarts, backed-up job queues, failed notifications, and database changes. I would also test recovery from backups.

To see how the running application was behaving, I would measure how long it took to accept records, how often image downloads failed, and how many jobs were waiting. A working dashboard alone would not tell me whether those parts were keeping up.

What I learned

Getting the first message into the database was only the beginning. The harder work was making record storage, background jobs, camera controls, user accounts, and notifications work together.

The project gave me experience building and deploying a web application across two locations. Looking back, the biggest change I would make is to separate the web application from the processes it manages, then test what happens when each part stops or restarts.

Project facts

  • Role: Project author and full-stack developer.
  • Deployment: Five rebranded Dahua IP cameras, two Rekor Scout agents, and two locations.
  • Technology: Python, Flask, SQLAlchemy, Alembic, Redis, RQ, REST/webhooks, Linux, Gunicorn, SMTP, Twilio, and Dahua camera integration.
  • Source: OpenALPR-Webhook on GitHub