🇧🇷 Português · 🇺🇸 English
Desenvolvi este projeto para consolidar minha experiência com integrações de webhook: recebo leituras LPR de uma câmera Intelbras, persisto os dados em PostgreSQL e exponho um painel web + API REST para acompanhar as entradas em tempo real.
git clone https://github.com/lucas-hochmann-rosa/intelbras-lpr-webhook.git
cd intelbras-lpr-webhook
python -m venv venv
venv\Scripts\activate
pip install -r requirements.txt
copy .env.example .env # edite as variáveis de ambiente (PostgreSQL é obrigatório)
python main.py
Detalhes de cada passo nas seções abaixo.
O intelbras-lpr-webhook recebe eventos POST enviados pela função Push de uma câmera Intelbras LPR, valida e deduplica as leituras, salva os metadados e a imagem da captura em PostgreSQL, e disponibiliza tudo por uma API REST e um dashboard web responsivo (tema claro/escuro).
- Recebimento do webhook LPR - aceita os dois prefixos de rota citados na documentação oficial da câmera (
/NotificationInfo/...e/Notification/...), evitando404independente do firmware. - Persistência em PostgreSQL via SQLAlchemy.
- Deduplicação de leituras repetidas da mesma placa em uma janela de 30 segundos.
- Armazenamento das imagens de captura em disco, com limpeza automática das mais antigas.
- API REST para consulta do histórico de entradas (
/api/records) com filtros por placa e período. - Dashboard web com filtros, tabela em tempo real, preview de imagem e indicador de entradas não lidas.
- Controle de acesso por IP para o dashboard.
- Terminal organizado com Rich: banner, resumo de configuração e URLs de acesso ao subir o servidor; log estruturado em JSON Lines gravado em disco.
- Arquitetura
- Mapa dos Módulos
- Tecnologias utilizadas
- Como funciona o webhook
- Logging e terminal
- Regras da construção do projeto
- Requisitos
- Instalação
- Configuração de Ambiente
- Execução
- Endpoints Principais
- Testes Locais Rápidos
- Segurança
- Quando parar de funcionar
- Avisos
- Autor
- Licença
intelbras-lpr-webhook/
├── main.py # Ponto de entrada: carrega .env, inicializa banco e sobe o servidor
├── requirements.txt
├── .env.example
├── docs/
│ └── intelbras-lpr-push-webhook.md # Referência técnica dos payloads da câmera
├── scripts/
│ └── send_test_plate.py # Simulador de webhook para testes locais
└── src/
└── lpr_webhook/
├── app.py # Application factory (Flask + CORS + blueprints)
├── config.py # Leitura e validação das variáveis de ambiente
├── logging_setup.py # Console colorido (Rich) + arquivo JSON Lines
├── terminal.py # Banner, resumo de configuração e URLs (Rich)
├── database.py # Engine PostgreSQL (sem fallback local)
├── models.py # Modelo ORM (PlateReading)
├── repository.py # Consultas de leitura sobre o histórico
├── network.py # IP local, IP do cliente e allowlist
├── services/
│ ├── plate_capture.py # Valida, deduplica e persiste uma leitura
│ └── image_cleanup.py # Limpeza de imagens de captura antigas
├── routes/
│ ├── camera.py # Endpoints chamados pela câmera (webhook)
│ ├── api.py # API REST de consulta (/api/records)
│ └── web.py # Dashboard e favicon
├── templates/
│ └── index.html # Dashboard web
└── static/
├── camera.png # Favicon do painel
└── captures/ # Imagens salvas das leituras (execução)
- main.py: bootstrap do serviço - carrega ambiente, conecta ao PostgreSQL e sobe o servidor Waitress.
- src/lpr_webhook/app.py: application factory, monta o Flask com CORS e registra as blueprints.
- src/lpr_webhook/routes/: camada HTTP, dividida por responsabilidade (câmera, API, web).
- src/lpr_webhook/services/: regras de negócio (captura de leitura, limpeza de imagens).
- src/lpr_webhook/database.py + models.py + repository.py: camada de persistência.
- src/lpr_webhook/logging_setup.py + terminal.py: observabilidade - logs estruturados e saída de terminal.
Runtime: Python 3.10+
Web: Flask, Flask-CORS, Waitress (servidor WSGI de produção)
Persistência: SQLAlchemy + psycopg2-binary (PostgreSQL)
Observabilidade: Rich (console colorido e tracebacks legíveis) + logging estruturado em JSON Lines
Configuração: python-dotenv
Frontend: HTML + CSS puro (fonte Inter via Google Fonts), sem framework JS - servido pelo próprio Flask via Jinja
A câmera Intelbras usa a função Push: ao detectar uma leitura, ela mesma envia um POST com o payload em JSON para o endpoint configurado nela - a aplicação atua de forma passiva, apenas recebendo e respondendo HTTP 200.
A documentação oficial é inconsistente quanto ao prefixo das rotas: os exemplos de código usam /NotificationInfo/..., mas as tabelas de glossário registram /Notification/... (sem o sufixo "Info"). Este projeto registra os dois prefixos para os três endpoints, para não depender de qual forma o firmware da câmera realmente usa:
POST /NotificationInfo/TollgateInfo(e/Notification/TollgateInfo) - evento principal, com os dados da placa, do veículo e (opcionalmente) o snapshot em base64.POST /NotificationInfo/KeepAlive(e/Notification/KeepAlive) - heartbeat periódico do dispositivo.POST /NotificationInfo/DeviceInfo(e/Notification/DeviceInfo) - metadados do dispositivo.
As respostas seguem o contrato oficial da câmera (objeto plano com Result/DeviceID/PlateNumber no TollgateInfo, corpo vazio no KeepAlive). Veja docs/intelbras-lpr-push-webhook.md para o detalhamento completo dos payloads, incluindo autenticação HTTP Digest e o modo alternativo Subscribe.
- Console:
RichHandlercolore por nível (INFO/WARNING/ERROR) e formata tracebacks de forma legível; o boot do servidor imprime um banner, um resumo da configuração carregada e uma tabela com as URLs prontas para uso. - Arquivo: cada evento é gravado como uma linha JSON em
logs/app.jsonl(rotação automática em 5 MB, até 5 arquivos), fácil de buscar (grep), parsear ou enviar para uma ferramenta de observabilidade. - O nível (
INFO/WARNING/ERROR) e o nome do logger (LPR_WEBHOOK.database, por exemplo) seguem o padrão em inglês do próprio módulologging; a mensagem em si - o texto explicando o que está acontecendo, tipo "Conectando ao PostgreSQL..." - fica em português, igual à UI do dashboard.
- Identificadores, funções e estrutura de arquivos ficam em inglês.
- Mensagens de log ficam em português (nível e nome do logger seguem o inglês padrão do módulo
logging); a resposta da câmera (Result/DeviceID/PlateNumber) segue o contrato oficial, em inglês, por ser um protocolo externo. - Comentários no código ficam em português, reservados para decisões não óbvias - o "porquê", não o "o quê".
- Toda interação com a câmera fica isolada em
src/lpr_webhook/routes/camera.pyesrc/lpr_webhook/services/plate_capture.py. - Nenhuma credencial é versionada:
.envfica de fora do repositório, só.env.exampleé versionado.
- Python >= 3.10
- PostgreSQL ativo e acessível - obrigatório, não há fallback local
Sem PostgreSQL configurado ou acessível, o servidor não sobe:
main.pyfalha rápido com uma mensagem clara em vez de continuar em um estado degradado.
git clone https://github.com/lucas-hochmann-rosa/intelbras-lpr-webhook.git
cd intelbras-lpr-webhook
python -m venv venv
venv\Scripts\activate
pip install -r requirements.txt
copy .env.example .env
git clone https://github.com/lucas-hochmann-rosa/intelbras-lpr-webhook.git
cd intelbras-lpr-webhook
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
No Linux,
psycopg2-binarycostuma exigir as bibliotecas de desenvolvimento do PostgreSQL para compilar dependências nativas (libpq). Se a instalação falhar, instale primeiro: Debian/Ubuntusudo apt install libpq-dev python3-dev; Fedorasudo dnf install libpq-devel python3-devel; Archsudo pacman -S postgresql-libs.
Depois de instalar (em qualquer sistema), edite o .env recém-criado com as credenciais do seu PostgreSQL antes de seguir para a seção Configuração de Ambiente.
Crie .env com base em .env.example:
DATABASE_URL=
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DB=intelbras_lpr_webhook
POSTGRES_USER=postgres
POSTGRES_PASSWORD=senha_aqui
WEBHOOK_PORT=8000
WEBHOOK_HOST=127.0.0.1
FRONTEND_ALLOWED_IPS=127.0.0.1,::1
| Variável | Padrão | Para que serve |
|---|---|---|
DATABASE_URL |
- | URL completa do PostgreSQL; se definida, tem prioridade sobre as variáveis POSTGRES_* |
POSTGRES_HOST / POSTGRES_PORT / POSTGRES_DB / POSTGRES_USER / POSTGRES_PASSWORD |
- | Credenciais do PostgreSQL. Obrigatório (via essas variáveis ou via DATABASE_URL) |
WEBHOOK_PORT |
- | Porta HTTP do servidor. Obrigatório |
WEBHOOK_HOST |
127.0.0.1 |
Host informativo usado nos logs |
FRONTEND_ALLOWED_IPS |
vazio | IPs/redes com acesso ao dashboard, separados por vírgula. Vazio libera todo mundo |
Importante: não publique o arquivo .env com credenciais reais - o .gitignore já o ignora.
python main.py
- Dashboard:
http://localhost:WEBHOOK_PORT/ - API:
http://localhost:WEBHOOK_PORT/api/records - Webhook:
http://localhost:WEBHOOK_PORT/NotificationInfo/TollgateInfo(e demais rotas da câmera)
| Método | Rota | Descrição |
|---|---|---|
| GET | / |
Dashboard web de monitoramento |
| POST | /NotificationInfo/TollgateInfo, /Notification/TollgateInfo |
Endpoint principal para eventos da câmera |
| POST | /NotificationInfo/KeepAlive, /Notification/KeepAlive |
Keep-alive da câmera |
| POST | /NotificationInfo/DeviceInfo, /Notification/DeviceInfo |
Informações do dispositivo |
| GET | /api/records |
Lista leituras, com filtros plate, start_date e end_date |
Resposta ao TollgateInfo (contrato oficial da câmera):
{"Result": true, "DeviceID": "1c11c9c4-8dbc-4391-0dd7-56764ba18dbc", "PlateNumber": "ABC1234"}
Para simular entradas da câmera e validar o dashboard sem depender do hardware:
python scripts/send_test_plate.py
FRONTEND_ALLOWED_IPSrestringe o acesso ao dashboard por IP/rede; deixe vazio apenas em ambientes de teste.- Nenhuma credencial fica hardcoded - tudo vem do
.env, que nunca é versionado. - O servidor falha rápido (não sobe) se o PostgreSQL não estiver configurado/acessível, evitando operar num estado inconsistente.
| Sintoma | Causa provável |
|---|---|
WEBHOOK_PORT não definido no .env |
Configure a porta no .env antes de iniciar |
| Servidor não inicia com erro de PostgreSQL | Verifique POSTGRES_*/DATABASE_URL no .env e se o PostgreSQL está acessível |
Dashboard retorna Acesso negado |
Seu IP não está em FRONTEND_ALLOWED_IPS |
| Leituras não aparecem no dashboard | Confira se a câmera está configurada para enviar Push para /NotificationInfo/TollgateInfo (ou /Notification/TollgateInfo) na porta correta |
Logs completos ficam em logs/app.jsonl (JSON Lines, um evento por linha).
Projeto pessoal construído para consolidar experiência prática com integrações de webhook e persistência de dados em ambiente real. Adapte os endpoints e o payload conforme o modelo exato da sua câmera Intelbras - consulte o manual técnico do fabricante em conjunto com docs/intelbras-lpr-push-webhook.md.
Lucas Hochmann Rosa
- Repositório: https://github.com/lucas-hochmann-rosa/intelbras-lpr-webhook
- GitHub: https://github.com/lucas-hochmann-rosa
- LinkedIn: https://www.linkedin.com/in/lucas-hochmann-rosa
Licenciado sob MIT. Você pode usar, modificar e distribuir, mantendo o aviso de copyright e atribuindo crédito a Lucas Hochmann Rosa.