Webhooks¶
A plataforma envia notificações HTTP em tempo real para o endpoint configurado no seu tenant sempre que um evento relevante ocorre.
Os webhooks são opcionais. O fluxo de integração funciona sem eles — você pode sempre consultar o status via
GET /api/v1/checkins/{id}.
Como funcionam¶
Quando um check-in é processado, a plataforma envia uma requisição POST com o evento para o seu endpoint:
Se o seu endpoint retornar erro (4xx, 5xx ou timeout), a plataforma realiza novas tentativas automaticamente com intervalos crescentes.
Configuração¶
O endpoint de webhook é configurado durante o onboarding. Para atualizar o endpoint ou a chave de assinatura, entre em contato com a equipe de suporte.
Estrutura do evento¶
Todos os eventos seguem o mesmo envelope JSON:
{
"eventId": "550e8400-e29b-41d4-a716-446655440000",
"eventType": "checkin.published",
"occurredAt": "2026-07-03T08:30:10Z",
"tenantId": "1a2b3c4d-0000-0000-0000-000000000001",
"correlationId": "2b3c4d5e-aaaa-bbbb-cccc-000000000002",
"payload": { ... }
}
| Campo | Tipo | Descrição |
|---|---|---|
eventId |
Guid | Identificador único do evento |
eventType |
string | Tipo do evento (ver tabela abaixo) |
occurredAt |
DateTime UTC | Momento em que o evento foi gerado |
tenantId |
Guid | Seu tenant |
correlationId |
Guid | Rastreabilidade — igual ao da requisição original |
payload |
object | Dados específicos do evento |
Headers HTTP¶
Cada chamada inclui os seguintes headers:
| Header | Descrição |
|---|---|
Content-Type: application/json |
Sempre JSON |
X-Signature: sha256={hash} |
Assinatura HMAC-SHA256 para verificar autenticidade |
X-Event-Type: {tipo} |
Tipo do evento (ex: checkin.published) |
X-Correlation-Id: {uuid} |
Rastreabilidade |
X-Tenant-Id: {uuid} |
Seu tenant |
Verificação de assinatura¶
Para garantir que o evento foi enviado pela plataforma (e não por terceiros), verifique a assinatura HMAC-SHA256 antes de processar.
Como verificar¶
- Leia o header
X-Signature(formato:sha256={hex}) - Compute
HMAC-SHA256(secret, corpo_raw)usando o secret fornecido no onboarding - Compare o resultado com o valor do header usando comparação em tempo constante
Exemplo em C¶
using System.Security.Cryptography;
using System.Text;
bool IsSignatureValid(string secret, string requestBody, string signatureHeader)
{
var expectedHex = signatureHeader.StartsWith("sha256=")
? signatureHeader[7..]
: signatureHeader;
var key = Encoding.UTF8.GetBytes(secret);
var data = Encoding.UTF8.GetBytes(requestBody);
var hash = HMACSHA256.HashData(key, data);
var computed = Convert.ToHexString(hash).ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(computed),
Encoding.UTF8.GetBytes(expectedHex));
}
Exemplo em Python¶
import hmac
import hashlib
def is_valid(secret: str, body: bytes, signature_header: str) -> bool:
expected = signature_header.removeprefix("sha256=")
computed = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(computed, expected)
Importante: use sempre comparação em tempo constante (
FixedTimeEquals/compare_digest) para prevenir ataques de timing.
Eventos disponíveis¶
Eventos de Check-in¶
| EventType | Quando é enviado |
|---|---|
checkin.received |
Check-in recebido pela plataforma |
checkin.identified |
Aluno identificado com sucesso |
checkin.authorization-started |
Verificação de autorização iniciada |
checkin.authorized |
Aluno autorizado |
checkin.registered |
Check-in registrado no sistema |
checkin.published |
Check-in aprovado e publicado (evento final do fluxo bem-sucedido) |
checkin.not-identified |
Aluno não encontrado — fluxo encerrado |
checkin.rejected |
Aluno sem autorização válida — fluxo encerrado |
Payloads dos eventos¶
checkin.published (fluxo bem-sucedido)¶
{
"eventId": "880e8400-e29b-41d4-a716-446655440003",
"eventType": "checkin.published",
"occurredAt": "2026-07-03T08:30:10Z",
"tenantId": "1a2b3c4d-0000-0000-0000-000000000001",
"correlationId": "2b3c4d5e-aaaa-bbbb-cccc-000000000002",
"payload": {
"tenantId": "1a2b3c4d-0000-0000-0000-000000000001",
"checkinId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"studentId": "9f8e7d6c-0000-0000-0000-000000000099",
"source": "Internal",
"checkinOccurredAt": "2026-07-03T08:30:00Z",
"correlationId": "2b3c4d5e-aaaa-bbbb-cccc-000000000002"
}
}
checkin.not-identified (aluno não encontrado)¶
{
"eventId": "660e8400-e29b-41d4-a716-446655440001",
"eventType": "checkin.not-identified",
"occurredAt": "2026-07-03T08:30:02Z",
"tenantId": "1a2b3c4d-0000-0000-0000-000000000001",
"correlationId": "2b3c4d5e-aaaa-bbbb-cccc-000000000002",
"payload": {
"tenantId": "1a2b3c4d-0000-0000-0000-000000000001",
"checkinId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"source": "Internal",
"rejectionReason": "STUDENT_NOT_FOUND",
"checkinOccurredAt": "2026-07-03T08:30:00Z",
"correlationId": "2b3c4d5e-aaaa-bbbb-cccc-000000000002"
}
}
checkin.rejected (aluno sem autorização)¶
{
"eventId": "770e8400-e29b-41d4-a716-446655440002",
"eventType": "checkin.rejected",
"occurredAt": "2026-07-03T08:30:05Z",
"tenantId": "1a2b3c4d-0000-0000-0000-000000000001",
"correlationId": "2b3c4d5e-aaaa-bbbb-cccc-000000000002",
"payload": {
"tenantId": "1a2b3c4d-0000-0000-0000-000000000001",
"checkinId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"studentId": "9f8e7d6c-0000-0000-0000-000000000099",
"source": "Internal",
"rejectionReason": "AUTHORIZATION_EXPIRED",
"checkinOccurredAt": "2026-07-03T08:30:00Z",
"correlationId": "2b3c4d5e-aaaa-bbbb-cccc-000000000002"
}
}
Campos do payload de check-in¶
| Campo | Tipo | Presente em | Descrição |
|---|---|---|---|
tenantId |
Guid | todos | Seu tenant |
checkinId |
Guid | todos | ID do check-in na plataforma |
source |
string | todos | Origem do check-in |
checkinOccurredAt |
DateTime UTC | todos | Momento físico do check-in (conforme enviado) |
correlationId |
string | todos | Rastreabilidade |
studentId |
Guid | eventos após identificação | ID do aluno. Ausente em received e not-identified. |
rejectionReason |
string | not-identified, rejected |
Motivo do encerramento |
Boas práticas para o seu endpoint receptor¶
- Responda com 2xx imediatamente — processe o evento de forma assíncrona se necessário
- Verifique sempre a assinatura antes de processar
- Trate duplicatas — o mesmo evento pode ser entregue mais de uma vez; use o
eventIdpara detectar duplicatas - Não confie apenas nos webhooks — consulte
GET /checkins/{id}como fallback se não receber um evento esperado - Retorne 2xx mesmo que você já tenha processado o evento — isso evita re-tentativas desnecessárias