Aparência
Inbound — Hardware Accelero → ionCLASS
O que é este documento — o detalhe do fluxo INBOUND (FLUXO A): tudo que o hardware Accelero empurra para o backend do ionCLASS por webhook — eventos de catraca (
entry/exit), chegada de responsável na portaria (guardian_arrival), sincronização de cadastro (roster), veículos e o ping de saúde. Cobre autenticação HMAC, deduplicação, o roteamento por tipo de evento e como cada evento é aplicado ao domínio.
Para a visão geral das duas direções e da abstração de driver, veja Visão geral. O sentido oposto (backend → hardware) está em Outbound.
1. Os quatro endpoints de webhook
Todos são POST, versionados em v1, chaveados pela escola na própria URL (a escola é resolvida a partir do {school} da rota) e autenticados por HMAC-SHA256 — nunca por token de sessão. São rotas públicas (sem autenticação de sessão): a autorização é a assinatura do corpo bruto.
| Endpoint | O que faz | Resposta |
|---|---|---|
POST api/v1/integrations/accelero/{school}/events | Recebe o evento (HMAC), grava o registro do evento recebido e aplica ao domínio numa fila | 202 { accepted, event_id } |
POST api/v1/integrations/accelero/{school}/roster | Recebe o cadastro (HMAC) e faz upsert de alunos/responsáveis | 200 { students[], skipped[] } |
POST api/v1/integrations/accelero/{school}/vehicles | Recebe a sincronização de um veículo (HMAC) e faz upsert/remoção | 202 { accepted } |
POST api/v1/integrations/accelero/{school}/ping | Responde o "pong" do teste de saúde (sem efeito) | 200 { pong: true } |
2. Autenticação HMAC (FLUXO A)
A assinatura é validada na fronteira da requisição, antes de qualquer processamento — assinatura ausente/errada devolve 403 e nada é gravado.
- O segredo é a chave de eventos (entrada) da escola (armazenada criptografada). Ela é separada da chave de comandos (saída) que assina o FLUXO B — cada direção tem sua própria chave.
- Cálculo:
esperado = HMAC-SHA256(corpo_bruto_da_requisição, chave de eventos (entrada))
confere = comparação em tempo constante entre `esperado` e o header `X-Accelero-Signature`- Qualquer um destes → 403 (e um registro de skip): escola inexistente na rota, escola sem integração ativa, chave de entrada nula, header ausente, ou assinatura divergente.
- O
rosterreusa a mesma verificação; opinginbound (o "pong") é assinado com a mesma chave de entrada, provando que o canal plugin → backend está saudável.
⚠️ O HMAC é calculado sobre a chave de eventos (entrada) (o valor em claro, descriptografado). Ambos os lados — plugin e backend — precisam do mesmo segredo em claro; a criptografia é só o repouso no banco.
3. Webhook de eventos: events
3.1 Recepção — ack rápido
O backend extrai event_type, event_id, student_external_id, occurred_at e o payload bruto inteiro, e:
- Deduplica por
(sistema, event_id). Há um índice único sobre esse par (janela ≥ 24h por FR-105, mas o índice a garante permanentemente): se já existe um registro com o mesmoevent_id, ele é retornado sem reprocessar — reenvio do mesmo evento nunca duplica efeito. - Resolve o aluno por matrícula =
student_external_id, escopado à escola. Essa é a chave de correlação de identidade (matrícula ==student_external_id==pesCodigo/education->idno Accelero). Sestudent_external_idvier mas nenhum aluno casar, grava o registro assim mesmo com o aluno vazio e loga um skip (o processamento depois marca como falha quando o tipo exige aluno). - Grava o registro do evento recebido com status "recebido" e o payload bruto completo.
- Enfileira o processamento (assíncrono) e devolve
202imediatamente — o webhook responde rápido; o trabalho pesado acontece na fila.
Por que persistir antes de processar? O registro é a prova durável do que o hardware disse, independente de o processamento dar certo. Ele guarda o payload bruto, o estado do processamento, o carimbo de conclusão e o eventual erro, e é a mesma linha que o dedupe consulta.
3.2 Aplicação — roteia por tipo
O processamento recarrega o evento pelo id; se já tiver sido concluído, retorna (idempotente). Envolve o tratamento num try/catch: sucesso → marca como processado; falha → marca como falho e re-lança o erro, para a fila reprocessar erros genuínos (ver Runbook de tempo real).
roteia por event_type:
'exit' → trata a saída física
'entry' → reconcilia a entrada
'guardian_arrival' → registra a chegada do responsável (documento no payload bruto)
default → tipo inválido → falha → retry na filaentry/exit são o contrato original; guardian_arrival é aditivo — um plugin antigo que só mande entry/exit continua funcionando (validação in:entry,exit,guardian_arrival).
3.3 exit — saída física
A saída física sempre notifica os responsáveis, e então resolve o domínio de retirada (ver o funil dual-source em Ciclo de retirada):
- Passe solo autorizado — se o aluno tem um passe de retirada solo aprovado para hoje, é saída autorizada, não retirada: transiciona para
off_premises, consome o passe, encerra o sinal de chegada ativo, e não emite confirmação de retirada. - Já retirado — o operador venceu a corrida; grava uma confirmação secundária de auditoria (origem: hardware) e não muda estado.
- Caso geral — a catraca é autoritativa: funila pela resolução da corrida de origem com método
hardware. Resolve parapicked_upquando havia um sinal de chegada (responsável tinha sinalizado) ou paraoff_premises(saída não sinalizada) quando não havia.
O ator de um registro manual (admin, com a catraca offline) viaja no payload bruto, em
recorded_by_user_id; uma leitura real de hardware não tem usuário por trás (sem ator). Sem aluno mapeado, loga erro e não muda estado — o processamento marca o evento como falho.
3.4 entry — reconciliação de entrada
O aluno cruzou um leitor de entrada → fica on_premises. Cobre tanto a primeira chegada (off_premises → on_premises) quanto o retorno no mesmo dia após uma retirada (picked_up → on_premises) — o Accelero classifica entry/exit pelo canal físico do leitor, nunca pelo estado do aluno. O único evento ignorado é o de um aluno já on_premises (releitura duplicada). Não envolve retirada e não aparece em card público — só o monitor privado reflete a presença.
3.5 guardian_arrival — chegada de responsável
O gatilho que cobre quem chegou na portaria sem avisar pelo app: o responsável é reconhecido pela facial no leitor da própria escola. O documento (CPF) viaja no payload bruto (guardian_external_id), não em student_external_id — um CPF de responsável não é matrícula de aluno, e confundir os dois era exatamente o bug que fazia esses eventos sumirem.
- Resolve o responsável por CPF vinculado à escola; exige que ele tenha conta de app — sem conta, não há usuário a quem vincular o sinal (loga skip).
- Cria um sinal de chegada com origem facial, sem geofence (a leitura na catraca já é a prova de presença) e sem filtro de presença: varre todos os filhos com vínculo aprovado para
awaiting_pickup— inclusive os que o sistema achavaoff_premises/picked_up—, porque a facial prova que o adulto está na portaria e o estado de retirada pode estar desatualizado. - Janela de 5 min de idempotência, igual à do app: se já há sinal ativo recente do mesmo responsável (inclusive um sinal do app), ele é retornado intacto — o sinal do app vence o desempate por carregar intenção/detalhe que a catraca não tem.
- Emite o evento de sinal de chegada (+ versão pública) e notifica a portaria/monitor.
4. Fluxo webhook → fila → aplicação (ASCII)
Accelero (catraca/facial)
│ POST /api/v1/integrations/accelero/{school}/events
│ header X-Accelero-Signature = HMAC-SHA256(body, chave de entrada)
▼
Verificação HMAC na fronteira da requisição
│ HMAC inválido/ausente ─────────────► 403 (nada gravado)
│ HMAC ok
▼
Recepção do evento
│ dedupe (sistema, event_id) ────► duplicado: retorna o registro, sem efeito
│ resolve o aluno por matrícula = student_external_id
│ grava o registro do evento recebido (payload bruto, status=recebido)
│ enfileira o processamento assíncrono
▼
202 { accepted, event_id } (webhook responde rápido)
── fila ───────────────────────────────────────────────────────
Processamento assíncrono
│ roteia por event_type
│ exit → trata a saída ────────► resolução da corrida / off_premises / conf. secundária
│ entry → reconcilia a entrada ─► on_premises + broadcast
│ guardian_arrival → registra a chegada ───► sinal de chegada (facial) no board
│ sucesso → marca como processado
│ falha → marca como falho + re-lança → retry na fila
▼
broadcast em school.{id}.monitor (StudentStateChanged) + notificações5. Webhook de cadastro: roster
O hardware é a fonte do cadastro físico: o plugin empurra o roster (em lotes) e o backend faz upsert reusando a mesma lógica do import CSV, idempotente por escola + matrícula para alunos e por CPF para responsáveis.
- Devolve o mapa matrícula → identificador (UUID) do aluno deste lote, para o plugin popular seu identity map local (necessário para resolver o identificador interno do aluno em chamadas outbound).
- Aluno sem matrícula ou sem foto é pulado (coletado em
skipped[]), não derruba o lote. - Vínculos que estouram o limite de responsáveis da escola são coletados em
skipped[](chave aditiva — plugin antigo ignora). Matrículas referenciadas por um responsável mas trazidas num lote anterior são hidratadas do banco antes do vínculo (links cross-lote não se perdem). - Se o responsável trouxer
vehicles[], eles são sincronizados uma vez por CPF a partir do roster.
6. Webhook de veículos: vehicles
Mudança de um veículo (upsert/delete) vinda do Accelero. O backend resolve o responsável por CPF (precisa estar vinculado à escola) e faz upsert/remoção do veículo do responsável, auditado; 202 rápido, idempotente por responsável + placa. Responsável desconhecido/não vinculado é no-op (não erro) — placa de alguém que a escola não conhece não é nada a espelhar.
Este fluxo nunca enfileira chamada outbound — é justamente o que impede o loop do sync bidirecional: só as ações de app/admin empurram para fora; escritas inbound param aqui. O
deletepromove o veículo mais antigo a favorito se removeu o favorito (espelha a remoção de veículo do lado do app).
7. Webhook de saúde: ping — o "pong"
O endpoint de ping só devolve { pong: true } — sem efeito colateral. Ele é o lado inbound do ping-pong de saúde: quando o admin clica "Testar integração" no painel, o backend faz o ping outbound (FLUXO B, ver Outbound); o plugin, ao responder, chama de volta este endpoint assinado com a chave de eventos (entrada), provando que o canal plugin → backend também está de pé. É a única rota inbound sem persistência nem idempotência.
8. O registro do evento recebido
| Campo | Papel |
|---|---|
identificador do sistema + event_id | par do índice único (dedupe) |
event_type | entry / exit / guardian_arrival |
student_external_id | matrícula crua vinda do hardware (= matrícula do aluno) |
| aluno resolvido | resolvido da matrícula (vazio até casar) |
hora do evento (occurred_at) | ≈ agora numa leitura real; anterior num registro manual |
| payload bruto | corpo bruto inteiro — inclui guardian_external_id e recorded_by_user_id |
| estado do processamento | ciclo de vida: recebido → processado / falho, com carimbo e erro |
Ver também
- Visão geral — abstração de driver, identidade e o mapa das duas direções
- Outbound — o sentido backend → hardware
- Ciclo de retirada — o funil dual-source do
exit - Runbook de tempo real — filas e reprocessamento
- Glossário — termos (matrícula, sinal de chegada, estado de retirada…)