Webhooks
Um webhook é o caminho inverso da integração: em vez de você consultar a Nilo, a Nilo chama você quando alguma coisa muda. Você registra uma URL e o tipo de recurso a observar, e passa a receber uma requisição a cada alteração.
No FHIR isso é o recurso
Subscription.
Só o canal rest-hook é suportado, ou seja, uma chamada HTTP para uma URL sua. Os demais
canais do FHIR — e-mail, mensagem, websocket — recusam a chamada.
Criar uma assinatura
Uma assinatura viva por criteria. Não dá para ter duas assinaturas ativas ou pendentes
para o mesmo critério — e a comparação é da string inteira, filtros incluídos. Desligue a
primeira antes de criar outra.
criteria aceita filtros
Além do tipo de recurso, o criteria aceita parâmetros de busca depois de ?, e só notifica o
que casar com eles:
_include e _revinclude mudam o formato da notificação: em vez do recurso nu, você recebe
um Bundle do tipo searchset com o recurso e os relacionados. Veja
O que chega no seu endpoint.
Este recurso não tem identifier — o FHIR R4 não define o campo para ele. O identificador
da assinatura vem em meta.tag[0].code, e é por ele que a Nilo a reencontra. Guarde-o se
precisar correlacionar.
O aperto de mão
Uma assinatura não nasce ativa. Depois de responder ao seu POST, a Nilo chama a sua URL
com uma requisição de verificação:
- você cria a assinatura e recebe
status: requested; - a Nilo faz um
POSTna sua URL com umBundledo tipohistory, contendo umSubscriptionStatusdetype: handshake; - se a sua URL responder com sucesso (
2xx), a assinatura passa aactive; - se responder com erro, a assinatura passa a
error, e o motivo fica no campoerrordo recurso.
Enquanto a assinatura não estiver active, nenhuma notificação é entregue.
O aperto de mão pode chegar antes da resposta do POST. Ele é disparado durante o
processamento da criação, não depois dela — então o seu endpoint precisa estar de pé antes
de você chamar a API, e não pode depender de nada que só exista depois da resposta.
E não espere ver status: active na resposta da criação: ela devolve requested.
O que chega no seu endpoint
A notificação é um POST no seu endpoint, com o recurso que mudou no corpo:
A exclusão é notificada com o recurso que deixou de existir, marcado por meta.tag[].code
igual a DELETE. Trate esse caso separadamente: o corpo parece uma atualização normal.
E nela os filtros do criteria quase não são avaliados: como o recurso já não existe, só
active, status e o modificador :missing são aplicados. Um filtro mais complexo não é
considerado, e a notificação de exclusão chega mesmo assim.
Eventos de Bundle só geram notificação quando o Bundle é do tipo document. Uma
carga em lote não dispara webhook — o que dispara são as
escritas que ela faz.
Como conferir que a chamada veio da Nilo
Toda entrega leva uma assinatura HMAC do corpo da requisição:
Confira a assinatura em toda entrega. Calcule o HMAC-SHA256 do corpo cru da requisição
com o seu segredo e compare com o valor depois de sha256=. Uma chamada sem assinatura válida
não veio da Nilo.
O segredo
Você define o segredo mandando um cabeçalho chamado secret no channel.header[]:
Mande sempre o seu. Não recebendo um cabeçalho secret, a Nilo gera um internamente — e
esse valor gerado não volta na resposta nem nas leituras. Você fica com um webhook cuja
assinatura não consegue conferir.
O segredo que você mandar, esse, fica visível nas leituras seguintes da assinatura: trate a
leitura de Subscription como dado sensível.
Os demais cabeçalhos que você mandar em channel.header[] são repassados em toda entrega,
junto com os dois acima. É por aí que se manda um token estático, se o seu endpoint exigir um —
inclusive o próprio secret, que também vai repassado.
Prefira validar pelo X-Hub-Signature, não pelo cabeçalho secret: o primeiro prova que o
corpo não foi alterado; o segundo é só um valor fixo.
Um cabeçalho customizado com o nome Content-Type ou X-Hub-Signature sobrescreve o da
Nilo, em silêncio — e aí a sua própria verificação de assinatura quebra. Não use esses dois
nomes.
Se o seu ambiente estiver configurado para isso, a Nilo obtém um token OAuth2 e o envia em
Authorization, substituindo o que você tiver posto nesse cabeçalho. A configuração vale
também para o aperto de mão, então o seu endpoint de token precisa estar de pé antes de você
criar a assinatura. Fale com o Suporte.
O que a Nilo espera do seu endpoint
Responda rápido. A entrega tem tempo limite: 5 segundos para conectar e 15 para a resposta. Um endpoint lento é tratado como falha.
Responda 2xx assim que receber, e processe depois. Não faça trabalho pesado dentro da
requisição.
Entrega, retentativa e desativação
A Nilo espera 2xx. O que acontece quando não recebe:
A partir da terceira tentativa sem sucesso, a assinatura é desativada. Ela passa a
status: error, o motivo fica no campo error do recurso, e um alerta é enviado aos e-mails de
contact — com o criteria, o endpoint, o status HTTP, a mensagem de erro e o link para o
recurso que não pôde ser entregue.
Reativação automática
Uma assinatura em error não precisa ser recriada. No próximo evento daquele tipo de
recurso, a Nilo reenvia o aperto de mão sozinha; respondendo 2xx, a assinatura volta a
active e o evento é entregue normalmente.
O que ela não faz é reenviar os eventos ocorridos enquanto estava em error. Para não
perder dados, reconcilie o período com um GET do recurso correspondente, filtrando por
_lastUpdated.
Desligar uma assinatura
Mande status: off com o mesmo criteria. A assinatura ativa ou pendente daquele recurso é
encerrada, e o end do recurso passa a trazer o instante do desligamento.
Não havendo assinatura ativa nem pendente para aquele criteria, a chamada é recusada com
Not found webhook active or requested for criteria=….
Consultar assinaturas
Não filtre por status. Por uma particularidade do armazenamento, o status gravado é
sempre off, e o valor real é reposto só na hora de responder — então status=active devolve
vazio e status=off devolve tudo. Liste sem filtro, ou use o _tag.
Para reencontrar uma assinatura específica, use o _tag que veio em meta.tag[0]:
Diferente da busca, esta leitura não devolve um Bundle — mas também não devolve o recurso
nu. A resposta é um envelope com fullUrl, search e resource.
Erros
Recusa é 400. Neste recurso o issue[].code é sempre exception, sem expression, e o
details.text vem embrulhado — algo como
ValidationError: ['Only channel=rest-hook subscriptions are supported'].
Não compare o texto por igualdade: procure a mensagem como substring, ou simplesmente trate
400 como “assinatura recusada”.
Trocar o endpoint ou o critério
Não há atualização: desligue a assinatura e crie outra. Para não ficar sem cobertura entre as
duas chamadas, faça as duas numa carga em lote, com a entrada
de status: off primeiro — as entradas são processadas na ordem do array.
O que a integração não cobre
- canais que não sejam
rest-hook; - alterar uma assinatura: desligue e crie outra;
- reenvio dos eventos ocorridos enquanto a assinatura esteve em
errorouoff; - um
identifierpróprio: a assinatura é identificada pormeta.tag; - filtrar assinaturas por
statusna busca.

