Notificações por webhook
Um webhook avisa o seu sistema quando um estudo, um pedido ou uma execução muda de estado. Com ele, o seu sistema pode, por exemplo, começar uma análise assim que os resultados de um cenário ficarem prontos, sem consultar a API em intervalos.
Cada organização tem uma configuração de webhook, gerida por
GET/PUT/DELETE /api/v1/webhook. Consultar exige webhooks:read; configurar,
desligar, trocar o segredo e refazer entregas exigem webhooks:write.
Esta página trata do webhook configurado pela API. Configurar por PUT /webhook
substitui a URL e o segredo cadastrados em Perfil e
desliga os avisos escolhidos lá.
Configurar
Informe uma URL HTTPS pública e a lista de eventos:
curl -sS -X PUT "$MYRIA_API/api/v1/webhook" \
-H "Authorization: Bearer $MYRIA_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: configurar-webhook-001" \
-d '{"url":"https://hooks.example.test/myria","events":["execution.completed","execution.failed","operation.failed"]}'
O corpo aceita só url e events. events não pode ser vazio nem repetir
eventos; um evento desconhecido retorna 400 invalid_webhook_event.
Todo PUT gera um segredo novo, mesmo quando só a lista de eventos muda, e
cancela as entregas pendentes. A resposta traz a configuração (url,
events, enabled, configured_at, secret_last_rotated_at), o segredo em
secret, secret_one_time: true e pending_deliveries_cancelled, com um aviso
em pending_deliveries_warning quando alguma entrega foi cancelada. Guarde o
segredo na hora: GET /webhook nunca o devolve.
Repetir a mesma requisição com a mesma Idempotency-Key devolve byte a byte a
primeira resposta, inclusive o mesmo segredo. Uma chave nova com o mesmo corpo
é um novo PUT e gera outro segredo.
GET /webhook devolve a configuração atual, ou 404 se não houver webhook
configurado.
Para trocar só o segredo, use POST /api/v1/webhook/rotate-secret com uma
nova Idempotency-Key. A troca também cancela as entregas pendentes e
responde 404 se não houver webhook configurado. Atualize o segredo no seu
sistema antes de refazer entregas: uma nova entrega é assinada com o segredo
atual.
Eventos
| Evento | Quando é enviado |
|---|---|
study.updated | O nome, a descrição ou a versão de referência de um estudo mudou. |
study.deleted | Um estudo foi marcado como excluído. |
operation.succeeded | Um pedido (Operation) terminou com sucesso. |
operation.failed | Um pedido falhou. |
operation.cancelled | Um pedido foi cancelado. |
operation.attention_required | Um pedido precisa de análise. |
execution.started | Uma execução começou. |
execution.completed | Uma execução terminou com sucesso. |
execution.failed | Uma execução falhou. |
execution.stopped | Uma execução foi parada. |
execution.attention_required | Uma execução precisa de análise. |
execution.completed, execution.failed e execution.stopped só são enviados
depois que o encerramento foi confirmado. execution.attention_required indica
que a execução precisa de análise, por exemplo porque um resultado obrigatório
não foi coletado; não o trate como autorização para iniciar outra execução.
Formato da notificação
Cada notificação é um POST com corpo JSON:
{
"id": "4dfb20e7-94d9-4232-a510-0f0efef401b5",
"type": "execution.completed",
"created_at": "2026-09-30T13:20:00+00:00",
"organization": {"id": 12, "slug": "minha-org", "name": "Minha Organização"},
"data": {
"execution_id": "exec_0123456789abcdef0123456789abcdef",
"study_key": "minha-org-191",
"operation_id": "op_0123456789abcdef0123456789abcdef",
"status": "succeeded",
"status_url": "/api/v1/executions/exec_0123456789abcdef0123456789abcdef",
"reason_codes": []
}
}
id é o identificador do evento e created_at, o momento em que ele
aconteceu. O conteúdo de data depende do tipo:
| Eventos | Campos de data |
|---|---|
execution.* | execution_id, study_key, operation_id, status (running, succeeded, failed, cancelled ou attention_required), status_url, reason_codes |
operation.* | operation_id, status, status_url, kind, domain |
study.updated | study_key, changed_fields (name, description ou baseline), operation_id |
study.deleted | study_key, name, deleted_at, operation_id |
execution.stopped chega com status: "cancelled", o mesmo estado que
GET /api/v1/executions/{execution_id} mostra. reason_codes é sempre uma
lista, vazia quando não há causa a informar, e contém só códigos estáveis.
Os cabeçalhos de cada notificação são:
Content-Type: application/json
User-Agent: Myria-Webhooks/2.0
X-Myria-Event: execution.completed
X-Myria-Event-ID: 4dfb20e7-94d9-4232-a510-0f0efef401b5
X-Myria-Delivery: 9507c9e7-291b-4e56-98c7-f307906554c6
X-Myria-Timestamp: 1790774400
X-Myria-Signature-256: sha256=<hex>
X-Myria-Event-ID é igual ao id do corpo. X-Myria-Delivery identifica a
entrega.
Validar a assinatura
A assinatura é um HMAC-SHA256 do timestamp, um ponto e os bytes originais do corpo, com o segredo do webhook como chave:
HMAC-SHA256(secret, timestamp + "." + raw_body)
O resultado, em hexadecimal, vem em X-Myria-Signature-256 depois de
sha256=. Ao receber uma notificação:
- calcule a assinatura sobre o corpo bruto, antes de interpretar o JSON, com o
valor de
X-Myria-Timestamp; - compare com
X-Myria-Signature-256usando uma comparação de tempo constante; - confira se o id do evento (
X-Myria-Event-ID) já foi processado e ignore repetições; - grave o evento e só então responda com
2xx.
Cada tentativa de envio, inclusive as novas tentativas automáticas, leva um
X-Myria-Timestamp novo e uma X-Myria-Signature-256 calculada com esse
timestamp. O corpo, X-Myria-Delivery e X-Myria-Event-ID não mudam entre as
tentativas. Recuse notificações com timestamp de mais de 5 minutos em relação
ao seu relógio e deduplique pelo id do evento: juntos, os dois bloqueiam
repetições maliciosas sem perder as novas tentativas legítimas.
Falhas e novas tentativas
Uma entrega é bem-sucedida quando o seu endpoint responde 2xx em até 3
segundos para conectar e 10 segundos para responder. Qualquer outra resposta,
inclusive redirecionamentos 3xx, conta como falha: o Myria não segue
redirecionamentos.
Depois de uma falha, o Myria tenta de novo até completar 8 tentativas, com intervalos de 60 segundos, 5 minutos, 15 minutos, 1 hora, 4 horas, 12 horas e 24 horas.
GET /api/v1/webhook/deliveries lista as entregas, com paginação por cursor.
Cada item traz id (o id da entrega, o mesmo de X-Myria-Delivery),
event_id, event, status, attempts, created_at e delivered_at:
status | Significado |
|---|---|
pending | Aguarda envio. |
processing | Está sendo enviada agora. |
retrying | Falhou e aguarda nova tentativa automática. |
delivered | O seu endpoint respondeu com sucesso. |
failed | As tentativas automáticas acabaram sem sucesso. |
cancelled | Foi cancelada, por exemplo, ao trocar a configuração ou o segredo. |
Para enviar um evento de novo:
curl -sS -X POST \
"$MYRIA_API/api/v1/webhook/deliveries/$DELIVERY_ID/redeliver" \
-H "Authorization: Bearer $MYRIA_TOKEN" \
-H "Idempotency-Key: refazer-entrega-001"
redeliver cria uma entrega nova, com outro X-Myria-Delivery, timestamp e
assinatura novos (calculada com o segredo atual) e o mesmo id de evento e o
mesmo corpo. A resposta é 202 Operation. Se o evento não estiver mais entre
os eventos configurados, ou o webhook estiver desligado, a resposta é 409
webhook_event_disabled.
Desligar
DELETE /api/v1/webhook, com webhooks:write e Idempotency-Key, apaga a
URL, os eventos e o segredo, cancela as entregas pendentes e mantém o histórico
de entregas. A resposta é 200 com enabled: false e
pending_deliveries_cancelled, também quando o webhook já estava desligado.
Requisitos da URL
A URL precisa usar HTTPS e estar acessível pela internet. O Myria valida o destino no cadastro e antes de cada entrega. Endereços de rede privada, redirecionamentos para rede privada, DNS rebinding e falha na revalidação bloqueiam o envio. Isso impede que um webhook seja usado para alcançar serviços internos.