Recebimento de Webhooks
A QI DTVM envia cada webhook como um POST com body JSON para a URL que você configurou. A requisição leva o header SIGNATURE, um JWT assinado com HS256 usando a Signature Key da sua configuração de webhook: uma chave simétrica, que só a QI e você conhecem.
Validar a assinatura
import json
from hashlib import md5
from jose import jwt
signature_key = "SUA_SIGNATURE_KEY"
webhook_url = "https://sua-empresa.com.br/webhooks/qi" # a URL cadastrada na configuração
def validate_webhook(headers: dict, raw_body: bytes) -> dict:
# jwt.decode recusa o token se a assinatura não bater com a sua Signature Key.
claims = jwt.decode(headers["SIGNATURE"], key=signature_key, algorithms=["HS256"])
if claims["uri"] != webhook_url:
raise ValueError("uri diferente da URL configurada")
if claims.get("payload_md5") != md5(raw_body).hexdigest():
raise ValueError("payload_md5 não corresponde ao body recebido")
return json.loads(raw_body)
O JWT decodificado traz:
| Campo | Descrição |
|---|---|
timestamp | Data e hora da assinatura, em UTC, no formato AAAA-MM-DDTHH:MM:SS. Muda a cada envio. |
method | Método HTTP da requisição: POST. |
uri | A URL de destino configurada para o seu webhook, completa. |
payload_md5 | Hash MD5 do body enviado. |
Calcule o MD5 sobre os bytes recebidos, antes de fazer o parse do JSON. Um re-encode do JSON depois do parse muda espaçamento e ordem dos campos, e o hash deixa de bater.
Formato do body
Todo webhook tem o mesmo envelope. O conteúdo de data depende do evento e está na página de webhooks de cada produto.
{
"webhook_type": "settlement.payment_batch_status_change",
"webhook_datetime": "2026-10-07T14:32:10Z",
"data": {}
}
| Campo | Descrição |
|---|---|
webhook_type | Evento, no formato <produto>.<evento>. |
webhook_datetime | Data e hora em que o evento foi gerado. É a mesma em todas as tentativas. |
data | Dados do evento: um objeto ou uma lista, conforme o evento. |
Não mapeie os webhooks de forma restrita: campos novos podem ser incluídos no envelope e em data a qualquer momento.
Header AGENT-KEY
O header AGENT-KEY traz a chave do agente dono da configuração de webhook, ou seja, a sua chave: o manager_key da gestora, o consultant_key da consultoria ou o assignor_key do cedente. Ele não identifica o fundo. Para saber de qual fundo é o evento, use os campos de data, como fund_class_key.
Entrega e tentativas
- A entrega conta como bem-sucedida quando a sua URL responde com status HTTP menor que 300 em até 30 segundos. Qualquer outro status, timeout ou erro de rede conta como falha.
- Cada evento é enviado até 4 vezes. Depois da 4ª falha, ele é marcado como falho e não é mais reenviado automaticamente.
- Responda rápido: grave o webhook e processe depois. O tempo que a sua aplicação leva para processar conta para os 30 segundos.
Um webhook que esgotou as tentativas pode ser reenviado pela QI DTVM. Fale com integracao.dtvm@qitech.com.br informando o período e o tipo de evento. O reenvio não é uma operação disponível na sua integração.
Duplicatas
Você pode receber o mesmo webhook mais de uma vez, por dois motivos:
- A sua aplicação recebeu o webhook, mas a resposta não chegou à QI dentro do prazo. O envio é repetido.
- O mesmo evento pode ter de ser entregue a mais de um agente: por exemplo, à gestora, à consultoria e ao cedente do fundo. Esses envios são tentados juntos e em sequência. Se a URL de qualquer um deles falhar, a tentativa inteira é repetida, e quem já tinha recebido recebe de novo.
O segundo caso significa que uma falha na URL de outro agente gera duplicatas na sua. Trate o recebimento como idempotente: a repetição chega com o mesmo body, byte a byte (o mesmo webhook_type, webhook_datetime e data), então o mesmo payload_md5. Guarde o payload_md5 dos webhooks já processados e descarte o que repetir. Não use o JWT do header para isso: o timestamp muda a cada envio.
Validação de origem
Além de validar a assinatura, você pode restringir a sua URL aos IPs de saída da QI:
| Ambiente | IP |
|---|---|
| Produção | 54.205.166.229 |
| Sandbox | 52.72.221.4 |