Webhooks
Receba na sua plataforma os eventos das aplicações que você criou, sem consultar a API periodicamente.
Pela API, a sua plataforma chama a HumanTrack. Os webhooks fazem o caminho inverso: a HumanTrack chama a sua plataforma quando algo acontece com as aplicações que você criou. Os webhooks fazem parte da base comum da API e hoje carregam eventos do módulo Instrumentos.
Com eles, você sabe que um paciente respondeu um instrumento ou que uma aplicação foi encerrada por inatividade sem consultar a API periodicamente.
Versão do contrato: apiVersion: v1.
Ativação
Não há endpoint de autocadastro. O endpoint que vai receber os eventos é registrado pela equipe HumanTrack, que devolve o secret usado para assinar as entregas. O secret é exibido uma única vez, no momento do cadastro: guarde-o com o mesmo cuidado da sua API Secret.
Requisitos do endpoint:
- URL
httpspública (httpé recusado); - não pode resolver para endereço privado, loopback ou link-local;
- deve responder em até 10 segundos.
Quais aplicações geram eventos
A regra é de interseção, e as duas condições precisam valer ao mesmo tempo:
- a aplicação foi criada por você via HumanTrack API; e
- a integração daquele profissional com você está ativa no momento do evento.
Consequências práticas:
- aplicações criadas diretamente na HumanTrack pelo profissional nunca geram evento para você;
- aplicações criadas por outro parceiro também não;
- se o profissional desconectar a integração, você para de receber eventos, inclusive das aplicações que você mesmo criou. Reconectando, volta a receber.
Submissões que pertencem a um envio em lote não geram evento.
Envelope
Toda entrega é um POST com Content-Type: application/json e este corpo:
{
"id": "0c9c2bd8-8a3a-4a2e-9a4a-4a9b7f7d1c11",
"type": "SUBMISSION_ANSWERED",
"apiVersion": "v1",
"occurredAt": "2026-08-05T14:32:11Z",
"data": {}
}| Campo | Descrição |
|---|---|
id | UUID do evento. Use este campo para deduplicar. |
type | Tipo do evento (veja o catálogo abaixo). |
apiVersion | Versão do contrato. Fixa em v1. |
occurredAt | Instante em que o fato ocorreu, em UTC. |
data | Conteúdo específico do evento. |
Cabeçalhos:
| Cabeçalho | Descrição |
|---|---|
X-HumanTrack-Event-Id | Mesmo valor de id, útil para correlacionar logs. Fica fora da assinatura, então não serve para deduplicar: use o id do corpo assinado. |
X-HumanTrack-Signature | Assinatura HMAC da entrega (veja abaixo). |
O payload é enxuto por design: carrega os identificadores e o mínimo para exibição. O detalhe completo (respostas, escores, gráficos) continua sendo consultado pela HumanTrack API.
Catálogo de eventos
SUBMISSION_ANSWERED
O paciente respondeu uma submissão de uma aplicação criada por você.
{
"id": "0c9c2bd8-8a3a-4a2e-9a4a-4a9b7f7d1c11",
"type": "SUBMISSION_ANSWERED",
"apiVersion": "v1",
"occurredAt": "2026-08-05T14:32:11Z",
"data": {
"professionalId": "3f7c1a54-6b2e-4a1d-8f0c-2b5e9d3a7c11",
"externalProfessionalId": "psm-8842",
"patientId": "9d2e4b70-1c3a-4f6e-9b8d-5a7c0e2f4d33",
"submissionId": 91824,
"applicationId": 4417,
"formTemplateId": "b1e7d9c2-4f3a-4d8e-9c1b-7a2e5f0d6c88",
"formTemplateTitle": "PHQ-9",
"createdByPartner": true
}
}| Campo | Descrição |
|---|---|
professionalId | ID do profissional na HumanTrack. |
externalProfessionalId | ID do mesmo profissional na sua plataforma, conforme informado na vinculação. |
patientId | ID do paciente na HumanTrack. |
submissionId | ID da submissão respondida. |
applicationId | ID da aplicação que gerou a submissão. |
formTemplateId / formTemplateTitle | Instrumento aplicado. |
createdByPartner | Sempre true no roteamento atual; existe para estabilidade do contrato. |
APPLICATION_CANCELED_IDLE
A aplicação foi encerrada automaticamente por inatividade do paciente.
{
"id": "5b1a7e30-2d4c-4b9f-8e6a-1c3d5f7a9b22",
"type": "APPLICATION_CANCELED_IDLE",
"apiVersion": "v1",
"occurredAt": "2026-08-05T03:10:00Z",
"data": {
"professionalId": "3f7c1a54-6b2e-4a1d-8f0c-2b5e9d3a7c11",
"externalProfessionalId": "psm-8842",
"patientId": "9d2e4b70-1c3a-4f6e-9b8d-5a7c0e2f4d33",
"applicationId": 4417,
"formTemplateId": "b1e7d9c2-4f3a-4d8e-9c1b-7a2e5f0d6c88",
"formTemplateTitle": "PHQ-9",
"reason": "UNANSWERED_SUBMISSIONS",
"createdByPartner": true,
"canceledAt": "2026-08-05T03:09:58.412345Z"
}
}| Campo | Descrição |
|---|---|
reason | UNANSWERED_SUBMISSIONS (submissões consecutivas sem resposta) ou UNANSWERED_NOTIFICATIONS (lembretes consecutivos sem reação). |
canceledAt | Instante do cancelamento. Uma aplicação pode ser cancelada, reaberta por uma resposta tardia e cancelada de novo; este campo distingue os ciclos. |
Verificação da assinatura
O header tem a forma:
X-HumanTrack-Signature: t=1754404331,v1=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08té o timestamp Unix (segundos) do envio;v1éHMAC-SHA256(secret, "<t>" + "." + <corpo bruto>), em hexadecimal minúsculo.
Dois pontos que quebram a verificação se forem ignorados:
- use o corpo bruto da requisição, byte a byte. Serializar de novo o JSON depois de lê-lo produz outra assinatura;
- compare em tempo constante (
hmac.compare_digest,crypto.timingSafeEqual,hmac.Equal), nunca com==.
Recomendamos também recusar entregas cujo t esteja muito distante do relógio atual (5 minutos é uma folga razoável), para limitar a reutilização de entregas antigas interceptadas (replay).
Em texto: leia o corpo bruto e o header X-HumanTrack-Signature; confira se t e v1 estão presentes e se t está dentro da tolerância; calcule o HMAC-SHA256 de t, um ponto e o corpo bruto com o secret; compare o resultado com v1 em tempo constante. Se algum passo falhar, recuse a entrega. Se a assinatura confere, deduplique pelo id do envelope e responda 2xx.
Exemplo em Python
import hashlib
import hmac
import time
def verify(secret: str, header: str, raw_body: bytes, tolerance_seconds: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
timestamp, received = parts.get("t"), parts.get("v1")
if not timestamp or not received:
return False
try:
timestamp_seconds = int(timestamp)
except ValueError:
return False
if abs(time.time() - timestamp_seconds) > tolerance_seconds:
return False
expected = hmac.new(
secret.encode(),
timestamp.encode() + b"." + raw_body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, received)
# Vetor de teste: reproduza este resultado para validar sua implementação.
if __name__ == "__main__":
secret = "9f2b1c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f809"
raw_body = b'{"id":"0c9c2bd8-8a3a-4a2e-9a4a-4a9b7f7d1c11"}'
signature = hmac.new(
secret.encode(), b"1754404331." + raw_body, hashlib.sha256
).hexdigest()
print(signature)
assert verify(secret, f"t=1754404331,v1={signature}", raw_body, tolerance_seconds=10**9)
print("assinatura verificada")Exemplo em Node.js
const crypto = require("crypto");
function verify(secret, header, rawBody, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2)));
const { t: timestamp, v1: received } = parts;
if (!timestamp || !received) return false;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > toleranceSeconds) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.`)
.update(rawBody)
.digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(received, "hex");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Em Express, o corpo bruto exige express.raw({ type: 'application/json' }) na rota do webhook; express.json() descarta os bytes originais.
Resposta esperada
Qualquer 2xx conta como sucesso. Responda rápido: processe de forma assíncrona e devolva 200 assim que registrar o evento. Qualquer outro status (ou tempo esgotado, ou erro de conexão) conta como falha e aciona a política de reenvio.
O corpo da resposta é ignorado.
Reenvio automático
A entrega é at-least-once, com até 8 tentativas por entrega. Cada tentativa tem até 10 segundos para receber resposta; qualquer status fora de 2xx, tempo esgotado ou erro de conexão conta como falha e dispara o reenvio. A assinatura segue o esquema descrito em Verificação da assinatura.
Em texto: quando o evento ocorre, a HumanTrack faz um POST assinado ao seu endpoint. Uma resposta fora de 2xx, um tempo esgotado ou um erro de conexão contam como falha, e a HumanTrack reenvia o mesmo evento, com o mesmo id. A entrega termina na primeira resposta 2xx recebida em até 10 segundos, ou depois de 8 tentativas sem sucesso.
Deduplicação
Como a entrega é at-least-once, o mesmo evento pode chegar mais de uma vez (por exemplo, se você responder 200 e a resposta se perder no caminho). Deduplique pelo campo id do envelope, depois de verificar a assinatura: ele está no corpo assinado e é estável entre tentativas e reenvios.
Não deduplique pelo header X-HumanTrack-Event-Id. Ele fica fora do HMAC, então uma entrega reutilizada pode chegar com esse header alterado sem invalidar a assinatura. Use-o apenas para correlacionar logs.
Comportamento operacional (não contratual)
Os pontos abaixo descrevem o comportamento operacional atual do disparador de webhooks e podem mudar sem uma nova versão do contrato. Não construa garantias da sua integração sobre eles.
Intervalo entre tentativas
O intervalo cresce com a quarta potência da tentativa (cerca de tentativa⁴ segundos), então a última tentativa cai por volta de 78 minutos após a primeira. Isso absorve uma publicação de versão ou uma instabilidade curta do seu lado.
Entregas esgotadas
Esgotadas as tentativas, a entrega fica marcada como EXHAUSTED. O evento continua registrado e pode ser reenviado manualmente pela equipe HumanTrack quando o seu endpoint voltar.
Desativação automática
Se 10 entregas consecutivas esgotarem todas as tentativas, o endpoint é desativado automaticamente e enviamos um e-mail para o contato técnico cadastrado do parceiro. Uma entrega bem-sucedida zera esse contador.
Enquanto desativado, o endpoint não recebe nada, mas os eventos continuam registrados do nosso lado.
Para reativar: corrija o endpoint, confirme que ele responde 2xx e avise a equipe HumanTrack. A reativação zera o contador de falhas, e os eventos do período podem ser reenviados sob demanda.
Retenção
Eventos entregues são mantidos por 90 dias e depois removidos. Um evento com entrega pendente ou esgotada não é removido enquanto essa entrega existir. Pedidos de reenvio precisam, portanto, cair dentro dessa janela.
Suporte
Para dúvidas sobre a integração, escreva para vinicius@humantrack.io.