HumanTrackDevelopers

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 https pú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:

  1. a aplicação foi criada por você via HumanTrack API; e
  2. 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": {}
}
CampoDescrição
idUUID do evento. Use este campo para deduplicar.
typeTipo do evento (veja o catálogo abaixo).
apiVersionVersão do contrato. Fixa em v1.
occurredAtInstante em que o fato ocorreu, em UTC.
dataConteúdo específico do evento.

Cabeçalhos:

CabeçalhoDescrição
X-HumanTrack-Event-IdMesmo 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-SignatureAssinatura 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
  }
}
CampoDescrição
professionalIdID do profissional na HumanTrack.
externalProfessionalIdID do mesmo profissional na sua plataforma, conforme informado na vinculação.
patientIdID do paciente na HumanTrack.
submissionIdID da submissão respondida.
applicationIdID da aplicação que gerou a submissão.
formTemplateId / formTemplateTitleInstrumento aplicado.
createdByPartnerSempre 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"
  }
}
CampoDescrição
reasonUNANSWERED_SUBMISSIONS (submissões consecutivas sem resposta) ou UNANSWERED_NOTIFICATIONS (lembretes consecutivos sem reação).
canceledAtInstante 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=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
  • t é 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:

  1. use o corpo bruto da requisição, byte a byte. Serializar de novo o JSON depois de lê-lo produz outra assinatura;
  2. 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).

Verificação da assinatura de uma entrega de webhook

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.

Entrega de um evento com reenvio após falha

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.

Nesta página