Webhooks
O Proton manda um POST assinado para o endereço da empresa quando algo acontece nas automações: uma execução
termina, um lote termina ou não roda, uma ocorrência abre, é atribuída ou resolvida, um runner cai ou volta. É o
caminho para a ferramenta de monitoramento, de chamados ou de mensagens da empresa. Para ler os mesmos eventos sob
demanda, use GET /v1/events.
Os webhooks fazem parte do plano Enterprise. O cadastro fica em Configurações > Alertas e webhooks, na aba Webhooks: o nome, o endereço e os eventos (ou todos).
O que chega
O envio segue o padrão Standard Webhooks:
POST https://monitoramento.exemplo.com/proton
content-type: application/json
user-agent: Proton-Webhooks/1.0
webhook-id: evt_1532
webhook-timestamp: 1760886000
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
{
"type": "run.finished",
"timestamp": "2026-10-19T15:00:00.000Z",
"version": "1",
"data": {
"organizationId": 12,
"link": "https://app.protoncloud.com.br/automations/center/dataset/40/cycle/7/dataset-run/981/details",
"run": {
"id": 981,
"status": "Failed",
"endReason": "technical_error",
"startedAt": "2026-10-19T14:58:00.000Z",
"finishedAt": "2026-10-19T15:00:00.000Z",
"durationMs": 120000,
"correlationId": "7f6c2d2e-5f0b-4a4a-9b9b-3c1f2a9e0d11",
"retryScheduled": false,
"link": "https://app.protoncloud.com.br/automations/center/dataset/40/cycle/7/dataset-run/981/details"
},
"automation": { "id": 5, "name": "Cadastro de fornecedor" },
"dataset": { "id": 40, "name": "Filial 01" },
"group": { "id": 3, "name": "Operação", "purpose": "operation" },
"cycle": { "id": 7, "name": "Outubro" },
"environment": { "id": 2, "name": "Produção", "production": true },
"runner": { "id": 9, "name": "rpa-01", "hostname": "rpa-01" }
}
}
Os eventos
| Tipo | Quando | Campos em data |
|---|---|---|
run.finished | Uma execução terminou | run (status, motivo do fim, início, fim, duração, Correlation ID), automation, dataset, group, cycle, environment, runner |
batch.finished | Um lote terminou | batch (total, passed, failed, stopped), group, cycle, environment, runner |
batch.not_run | Um agendamento não saiu | batch (com o reason), group, cycle, environment, runner |
exception.opened, exception.assigned, exception.resolved | Uma ocorrência da operação abriu, foi atribuída ou resolvida | exception (kind, status, occurrences, reason, errorSummary, assignee), automation, dataset, group, cycle, environment |
runner.offline, runner.online | Um runner caiu (depois de 2 minutos fora) ou voltou | runner (name, owner, hostname, offlineSince, shared) |
webhook.test | O teste pela tela | organizationId, message |
O motivo do fim da execução (endReason) é um destes: passed, business_error, technical_error,
environment_error, stopped, not_dispatched, runner_lost.
A versão do corpo (version) só muda se um campo sair ou mudar de sentido. Campo novo pode aparecer a qualquer
momento: trate o que não conhece como opcional.
Conferir a assinatura
O segredo do webhook (whsec_...) aparece uma vez só, no cadastro e na troca. A assinatura é o HMAC-SHA256 de
webhook-id.webhook-timestamp.corpo, com a chave em base64 que vem depois de whsec_, codificado em base64 e
precedido de v1,. Confira sempre o corpo exatamente como chegou, antes de interpretar o JSON, e recuse mensagem
com webhook-timestamp muito antigo (mais de 5 minutos).
- Python
- Node.js
import base64
import hashlib
import hmac
import time
def assinatura_valida(segredo: str, cabecalhos: dict, corpo: bytes, tolerancia: int = 300) -> bool:
chave = base64.b64decode(segredo.removeprefix("whsec_"))
id_da_mensagem = cabecalhos["webhook-id"]
momento = cabecalhos["webhook-timestamp"]
if abs(time.time() - int(momento)) > tolerancia:
return False
conteudo = f"{id_da_mensagem}.{momento}.".encode() + corpo
esperada = base64.b64encode(hmac.new(chave, conteudo, hashlib.sha256).digest()).decode()
for assinatura in cabecalhos["webhook-signature"].split(" "):
versao, _, valor = assinatura.partition(",")
if versao == "v1" and hmac.compare_digest(valor, esperada):
return True
return False
import crypto from 'node:crypto';
export function assinaturaValida(segredo, cabecalhos, corpo, tolerancia = 300) {
const chave = Buffer.from(segredo.replace(/^whsec_/, ''), 'base64');
const id = cabecalhos['webhook-id'];
const momento = cabecalhos['webhook-timestamp'];
if (Math.abs(Date.now() / 1000 - Number(momento)) > tolerancia) return false;
// corpo: o texto exatamente como chegou, antes do JSON.parse
const esperada = crypto.createHmac('sha256', chave).update(`${id}.${momento}.${corpo}`).digest('base64');
return cabecalhos['webhook-signature'].split(' ').some((assinatura) => {
const [versao, valor] = assinatura.split(',');
return versao === 'v1'
&& valor.length === esperada.length
&& crypto.timingSafeEqual(Buffer.from(valor), Buffer.from(esperada));
});
}
As bibliotecas do Standard Webhooks (para Python, Node.js, Java, Go, C# e outras) fazem essa conferência.
Entrega e novas tentativas
- Resposta: qualquer
2xxem até 10 segundos conta como entregue. Responda rápido e processe depois. - Novas tentativas: sem
2xx, o Proton tenta de novo com espera crescente (30 segundos, 1 minuto, 2 minutos... até 1 hora entre uma e outra), até 8 tentativas. - Repetição: o
webhook-idé o mesmo em todas as tentativas do mesmo evento. Guarde os ids recebidos e descarte o repetido. - Endereço: só
https, num endereço público. Redirecionamento não é seguido. - Acompanhamento: a aba Histórico de Alertas e webhooks mostra cada entrega, com o resultado, o status HTTP
e o erro, e permite reenviar a que falhou. O botão de teste manda um
webhook.test.