APIPreçosDocumentaçãoBlogParceirosContato
Voltar ao blog
Integration

Ponto biométrico em tempo real no seu ERP: Webhooks, middleware e 20 linhas de código

Envie registros de ponto biométrico para Odoo, SAP, ERPNext ou seu HRMS próprio em tempo real. Sem polling, sem imports CSV. Um webhook, uma função serverless e 20 linhas de código.

PunchConnect Team·Mar 28, 2026·9 min read

Introduction

Seus dispositivos biométricos coletam registros de ponto. Seu ERP calcula a folha de pagamento. Entre esses dois sistemas existe uma lacuna que custa às empresas horas de digitação manual toda semana. Algumas equipes de TI exportam CSV à meia-noite. Outras rodam crons que consultam os relógios de ponto a cada 5 minutos. Algumas desistiram e contrataram alguém para digitar os registros de ponto manualmente.

Nada disso é necessário. Com um webhook e uma função middleware de 20 linhas, cada batida chega no seu ERP no momento em que o dedo toca o leitor.

Este guia mostra exatamente como — com código pronto para produção para Odoo, SAP, ERPNext e sistemas HRMS próprios.

Por que imports batch estão prejudicando a precisão da sua folha

A abordagem tradicional: exportar dados de ponto do software do dispositivo, salvar como CSV, fazer upload no ERP, torcer para que o mapeamento não tenha quebrado nada. Equipes fazem isso diariamente, às vezes duas vezes por dia.

Os problemas se acumulam rápido:

- Atraso nos dados. Uma batida às 8:47 pode não chegar ao ERP até o batch da meia-noite. Cálculos de hora extra estão sempre um dia atrasados.
- Registros perdidos. Dispositivo fica offline durante a janela de exportação? Essas batidas desaparecem. Você descobre a falha quando a folha já foi processada.
- Erros manuais. Alguém mapeia o ID do funcionário 1042 para o registro errado no Odoo. Um mapeamento errado cascateia em contracheques incorretos.
- Sem trilha de auditoria. Quando aquela batida realmente chegou no ERP? Ninguém sabe.

A integração em tempo real elimina os quatro problemas. Cada batida chega no ERP em segundos, com um carimbo temporal verificável e uma trilha de auditoria limpa.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 380" fill="none" style="width:100%;max-width:800px;">
<text x="400" y="30" text-anchor="middle" fill="#94a3b8" font-size="17" font-weight="bold" font-family="system-ui">Import batch vs Webhook tempo real</text>
<!-- Batch side -->
<text x="200" y="65" text-anchor="middle" fill="#ef4444" font-size="15" font-weight="bold" font-family="system-ui">❌ Import batch tradicional</text>
<rect x="40" y="80" width="320" height="280" rx="12" stroke="#ef4444" stroke-width="1.5" fill="none" stroke-dasharray="5,4"/>
<rect x="60" y="100" width="130" height="40" rx="8" stroke="#64748b" stroke-width="1.5" fill="none"/>
<text x="125" y="125" text-anchor="middle" fill="#94a3b8" font-size="14" font-family="system-ui">Software REP</text>
<text x="200" y="125" text-anchor="middle" fill="#64748b" font-size="14" font-family="system-ui">→</text>
<rect x="220" y="100" width="120" height="40" rx="8" stroke="#64748b" stroke-width="1.5" fill="none"/>
<text x="280" y="125" text-anchor="middle" fill="#94a3b8" font-size="14" font-family="system-ui">Export CSV</text>
<text x="280" y="165" text-anchor="middle" fill="#64748b" font-size="14" font-family="system-ui">↓ (horas depois)</text>
<rect x="130" y="180" width="150" height="40" rx="8" stroke="#64748b" stroke-width="1.5" fill="none"/>
<text x="205" y="205" text-anchor="middle" fill="#94a3b8" font-size="14" font-family="system-ui">Upload manual</text>
<text x="205" y="245" text-anchor="middle" fill="#64748b" font-size="14" font-family="system-ui">↓ (propenso a erros)</text>
<rect x="155" y="260" width="100" height="40" rx="8" stroke="#ef4444" stroke-width="1.5" fill="none"/>
<text x="205" y="285" text-anchor="middle" fill="#ef4444" font-size="14" font-family="system-ui">ERP</text>
<text x="205" y="330" text-anchor="middle" fill="#ef4444" font-size="14" font-family="system-ui">⏱ 4-24h de atraso</text>
<text x="205" y="350" text-anchor="middle" fill="#ef4444" font-size="14" font-family="system-ui">📉 Registros perdidos</text>
<!-- Real-time side -->
<text x="600" y="65" text-anchor="middle" fill="#22d3ee" font-size="15" font-weight="bold" font-family="system-ui">✅ Webhook tempo real</text>
<rect x="440" y="80" width="320" height="280" rx="12" stroke="#22d3ee" stroke-width="1.5" fill="none"/>
<rect x="490" y="110" width="130" height="40" rx="8" stroke="#22d3ee" stroke-width="1.5" fill="none"/>
<text x="555" y="135" text-anchor="middle" fill="#22d3ee" font-size="14" font-family="system-ui">Relógio de ponto</text>
<text x="600" y="175" text-anchor="middle" fill="#22d3ee" font-size="14" font-family="system-ui">↓ instantâneo</text>
<rect x="490" y="190" width="130" height="40" rx="8" stroke="#a78bfa" stroke-width="1.5" fill="none"/>
<text x="555" y="215" text-anchor="middle" fill="#a78bfa" font-size="14" font-family="system-ui">PunchConnect</text>
<text x="600" y="255" text-anchor="middle" fill="#22d3ee" font-size="14" font-family="system-ui">↓ webhook (< 1s)</text>
<rect x="490" y="270" width="130" height="40" rx="8" stroke="#34d399" stroke-width="1.5" fill="none"/>
<text x="555" y="295" text-anchor="middle" fill="#34d399" font-size="14" font-family="system-ui">Seu ERP</text>
<text x="600" y="340" text-anchor="middle" fill="#34d399" font-size="14" font-family="system-ui">⚡ Entrega sub-segundo</text>
<text x="600" y="358" text-anchor="middle" fill="#34d399" font-size="14" font-family="system-ui">📊 Zero perda de dados</text>
</svg>

A arquitetura: Dispositivo → PunchConnect → Middleware → ERP

O padrão de integração é o mesmo independente do ERP:

1. O dispositivo biométrico registra uma batida (impressão digital, face, RFID)
2. PunchConnect recebe o evento e envia como webhook para seu endpoint
3. Seu middleware (função serverless ou servidor leve) recebe o webhook
4. O middleware mapeia e grava o registro de ponto na API do seu ERP

O middleware é onde a mágica acontece. Tipicamente são 20-50 linhas de código — um handler HTTP simples que transforma o payload do webhook PunchConnect no formato de ponto do seu ERP.

O payload webhook que você vai receber

Cada evento de ponto chega como JSON POST na sua URL webhook configurada:

O event_id é sua chave de idempotência — use-o para deduplicar em caso de retentativas.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 300" fill="none" style="width:100%;max-width:800px;">
<text x="400" y="30" text-anchor="middle" fill="#94a3b8" font-size="17" font-weight="bold" font-family="system-ui">Fluxo de integração middleware</text>
<rect x="30" y="70" width="150" height="70" rx="10" stroke="#22d3ee" stroke-width="2" fill="none"/>
<text x="105" y="98" text-anchor="middle" fill="#22d3ee" font-size="15" font-weight="bold" font-family="system-ui">1. Receber</text>
<text x="105" y="120" text-anchor="middle" fill="#94a3b8" font-size="14" font-family="system-ui">POST /webhook</text>
<line x1="180" y1="105" x2="220" y2="105" stroke="#64748b" stroke-width="1.5"/>
<rect x="220" y="70" width="150" height="70" rx="10" stroke="#a78bfa" stroke-width="2" fill="none"/>
<text x="295" y="98" text-anchor="middle" fill="#a78bfa" font-size="15" font-weight="bold" font-family="system-ui">2. Verificar</text>
<text x="295" y="120" text-anchor="middle" fill="#94a3b8" font-size="14" font-family="system-ui">Assinatura HMAC</text>
<line x1="370" y1="105" x2="410" y2="105" stroke="#64748b" stroke-width="1.5"/>
<rect x="410" y="70" width="150" height="70" rx="10" stroke="#f59e0b" stroke-width="2" fill="none"/>
<text x="485" y="98" text-anchor="middle" fill="#f59e0b" font-size="15" font-weight="bold" font-family="system-ui">3. Mapear</text>
<text x="485" y="120" text-anchor="middle" fill="#94a3b8" font-size="14" font-family="system-ui">ID → funcionário ERP</text>
<line x1="560" y1="105" x2="600" y2="105" stroke="#64748b" stroke-width="1.5"/>
<rect x="600" y="70" width="170" height="70" rx="10" stroke="#34d399" stroke-width="2" fill="none"/>
<text x="685" y="98" text-anchor="middle" fill="#34d399" font-size="15" font-weight="bold" font-family="system-ui">4. Gravar</text>
<text x="685" y="120" text-anchor="middle" fill="#94a3b8" font-size="14" font-family="system-ui">Criar no ERP</text>
<rect x="220" y="190" width="340" height="80" rx="10" stroke="#64748b" stroke-width="1.5" fill="none" stroke-dasharray="5,4"/>
<text x="390" y="218" text-anchor="middle" fill="#94a3b8" font-size="15" font-weight="bold" font-family="system-ui">Se falhar: 500 → PunchConnect retenta</text>
<text x="390" y="242" text-anchor="middle" fill="#64748b" font-size="14" font-family="system-ui">Backoff exponencial • 72h de retentativas • Pelo-menos-uma-vez</text>
<line x1="485" y1="140" x2="390" y2="190" stroke="#64748b" stroke-width="1" stroke-dasharray="4,3"/>
</svg>

json
{
"event": "attendance.punch",
"device_serial": "CZKE2241600045",
"employee_id": "1042",
"timestamp": "2026-03-28T08:47:12-03:00",
"direction": "in",
"method": "fingerprint",
"event_id": "evt_a8f3k2x9m1"
}

Integração #1: Odoo (JSON-RPC)

Odoo usa JSON-RPC para sua API. Aqui está um middleware completo — muito usado por integradores no Brasil e em Portugal:

Faça deploy no Railway, DigitalOcean ou qualquer plataforma serverless. Nossos clientes no Brasil colocaram isso pra funcionar em menos de uma hora.

> Dica: Mapeie employee_id do PunchConnect para o campo barcode de hr.employee no Odoo. É o mapeamento mais limpo — sem campos customizados.

python
# odoo_middleware.py — PunchConnect → Odoo ponto
import xmlrpc.client
from flask import Flask, request, jsonify
app = Flask(__name__)
ODOO_URL = "https://seu-odoo.com"
ODOO_DB = "sua-base"
ODOO_USER = "admin"
ODOO_PASS = "sua-api-key"
models = xmlrpc.client.ServerProxy(f"{ODOO_URL}/xmlrpc/2/object")
uid = xmlrpc.client.ServerProxy(f"{ODOO_URL}/xmlrpc/2/common").authenticate(
ODOO_DB, ODOO_USER, ODOO_PASS, {}
)
@app.route("/webhook", methods=["POST"])
def handle_punch():
data = request.json
employee = models.execute_kw(
ODOO_DB, uid, ODOO_PASS,
"hr.employee", "search_read",
[[["barcode", "=", data["employee_id"]]]],
{"fields": ["id"], "limit": 1}
)
if not employee:
return jsonify({"error": "funcionário não encontrado"}), 404
if data["direction"] == "in":
models.execute_kw(
ODOO_DB, uid, ODOO_PASS,
"hr.attendance", "create",
[{"employee_id": employee[0]["id"],
"check_in": data["timestamp"]}]
)
else:
open_att = models.execute_kw(
ODOO_DB, uid, ODOO_PASS,
"hr.attendance", "search",
[[["employee_id", "=", employee[0]["id"]],
["check_out", "=", False]]],
{"limit": 1}
)
if open_att:
models.execute_kw(
ODOO_DB, uid, ODOO_PASS,
"hr.attendance", "write",
[open_att, {"check_out": data["timestamp"]}]
)
return jsonify({"status": "ok"}), 200

Integração #2: SAP (OData / RFC)

Integrações SAP seguem o mesmo padrão mas usam a API OData ou chamadas RFC do SAP:

Para SAP on-premise: Use SAP Cloud Integration (CPI) como camada middleware. CPI pode receber webhooks PunchConnect via HTTPS e rotear para seu SAP on-premise via Cloud Connector.

> Conformidade Portaria 671 e CLT: A Portaria 671 do MTP regulamenta o REP-P (Registrador Eletrônico de Ponto por Programa). Com PunchConnect, cada batida fica registrada com carimbo temporal exato, método de verificação e dispositivo — documentação pronta para fiscalização do MTE. A CLT (Art. 74, §2º) exige controle de jornada para empresas com mais de 20 funcionários. A LGPD (Art. 11) classifica dados biométricos como sensíveis — PunchConnect envia apenas ID do funcionário e timestamp, nunca templates biométricos.

python
# sap_middleware.py — PunchConnect → SAP SuccessFactors
import requests
from flask import Flask, request, jsonify
app = Flask(__name__)
SAP_BASE = "https://api.successfactors.com"
SAP_COMPANY = "seuCompanyId"
SAP_USER = "api-user"
SAP_PASS = "api-password"
@app.route("/webhook", methods=["POST"])
def handle_punch():
data = request.json
payload = {
"userId": data["employee_id"],
"eventDate": data["timestamp"][:10],
"eventTime": data["timestamp"][11:19].replace(":", ""),
"eventType": "P10" if data["direction"] == "in" else "P20",
"terminalId": data["device_serial"]
}
response = requests.post(
f"{SAP_BASE}/odata/v2/EmployeeTime",
json=payload,
auth=(f"{SAP_USER}@{SAP_COMPANY}", SAP_PASS),
headers={"Content-Type": "application/json"}
)
if response.status_code in (200, 201):
return jsonify({"status": "ok"}), 200
return jsonify({"error": response.text}), 500

Integração #3: ERPNext (REST API)

ERPNext tem uma API REST limpa que torna esta integração direta:

> Dica ERPNext: Use o doctype Employee Checkin — ele tem auto-frequência integrada que calcula registros de Attendance a partir dos checkins. Não escreva diretamente em Attendance.

javascript
// erpnext_middleware.js — PunchConnect → ERPNext
const express = require("express");
const axios = require("axios");
const app = express();
app.use(express.json());
const ERPNEXT_URL = "https://seu-site.erpnext.com";
const API_KEY = "sua-api-key";
const API_SECRET = "seu-api-secret";
const erpnext = axios.create({
baseURL: ERPNEXT_URL,
headers: {
Authorization: `token ${API_KEY}:${API_SECRET}`,
"Content-Type": "application/json",
},
});
app.post("/webhook", async (req, res) => {
const { employee_id, timestamp, direction, event_id } = req.body;
try {
const { data: empList } = await erpnext.get(
`/api/resource/Employee?filters=[["attendance_device_id","=","${employee_id}"]]&fields=["name"]`
);
if (!empList.data.length) {
return res.status(404).json({ error: "funcionário não encontrado" });
}
const employeeName = empList.data[0].name;
await erpnext.post("/api/resource/Employee Checkin", {
employee: employeeName,
time: timestamp,
device_id: event_id,
log_type: direction === "in" ? "IN" : "OUT",
});
res.json({ status: "ok" });
} catch (err) {
console.error("Erro ERPNext:", err.response?.data || err.message);
res.status(500).json({ error: "falha gravação ERP" });
}
});
app.listen(3000);

Integração #4: HRMS próprio (banco de dados direto)

Se você desenvolveu seu próprio HRMS, a integração é ainda mais simples:

python
# custom_middleware.py — PunchConnect → HRMS próprio
from flask import Flask, request, jsonify
import psycopg2
import os
app = Flask(__name__)
conn = psycopg2.connect(os.environ["DATABASE_URL"])
@app.route("/webhook", methods=["POST"])
def handle_punch():
data = request.json
with conn.cursor() as cur:
cur.execute(
"SELECT 1 FROM attendance_events WHERE event_id = %s",
(data["event_id"],)
)
if cur.fetchone():
return jsonify({"status": "duplicado"}), 200
cur.execute(
"""INSERT INTO attendance_events
(event_id, employee_id, punch_time, direction, device_serial, method)
VALUES (%s, %s, %s, %s, %s, %s)""",
(data["event_id"], data["employee_id"],
data["timestamp"], data["direction"],
data["device_serial"], data["method"])
)
conn.commit()
return jsonify({"status": "ok"}), 200

Tratamento de casos extremos

Dispositivos offline: Quando um dispositivo fica offline, ele armazena as batidas localmente. Uma vez reconectado, PunchConnect entrega os eventos em ordem. Os carimbos temporais são da batida original, não da entrega.

Batidas duplas: Funcionários às vezes batem ponto duas vezes por acidente. Seu ERP deve tratar isso na lógica de negócio — por exemplo, ignorar um segundo check-in dentro de 60 segundos do primeiro.

Diferenças de fuso horário: PunchConnect entrega carimbos temporais em UTC. Converta para seu fuso local no middleware antes de gravar no ERP. No Brasil, atenção especial com os fusos (BRT -3, AMT -4, ACT -5).

Direção faltante: Alguns dispositivos antigos não informam a direção da batida. PunchConnect marca esses como direction: "unknown". Seu middleware deve inferir a direção do contexto.

FAQ

Quão rápido as batidas chegam no meu ERP?

Sub-segundo a partir do momento que PunchConnect recebe o evento. A latência total do dedo no leitor ao registro no ERP é tipicamente menos de 3 segundos.

O que acontece se meu middleware estiver fora do ar?

PunchConnect retenta com backoff exponencial por 72 horas. Quando seu middleware volta a ficar online, recebe todos os eventos em buffer na ordem. Nenhum dado é perdido.

Preciso de IP fixo para o endpoint webhook?

Não. PunchConnect entrega webhooks para qualquer URL HTTPS — funções serverless, plataformas cloud ou túneis como ngrok para desenvolvimento. Veja nosso guia sobre ponto biométrico sem IP fixo.

Posso integrar com múltiplos ERPs simultaneamente?

Sim. Registre múltiplas URLs webhook no PunchConnect e cada uma receberá cada evento independentemente.

Como gerencio 50+ dispositivos em múltiplas filiais?

O middleware não se importa com quantos dispositivos você tem — cada batida segue o mesmo caminho webhook → middleware → ERP. Veja nosso guia de gestão multi-filial.

---

Pronto para conectar seus dispositivos biométricos ao seu ERP? PunchConnect te dá uma API REST e webhooks prontos para usar. Comece seu teste gratuito de 7 dias — sem cartão de crédito. A maioria das equipes tem sua primeira integração funcionando em menos de uma hora.

*Já usa PunchConnect? Confira nosso guia de configuração webhook para configuração avançada, ou veja como a AgriWise integrou 24.000+ funcionários em 50+ filiais.*

Artigos relacionados

Ponto biométrico em tempo real no seu ERP: Webhooks, middleware e 20 linhas de código | PunchConnect