Skip to main content
Proton v5

Webhooks

Proton sends a signed POST to the company's address when something happens in the automations: a run finishes, a batch finishes or does not run, an exception opens, is assigned or resolved, a runner goes down or comes back. This is the path to the company's monitoring, ticketing or messaging tool. To read the same events on demand, use GET /v1/events.

Webhooks are part of the Enterprise plan. They are registered in Settings > Alerts and webhooks, on the Webhooks tab: the name, the address and the events (or all of them).

What arrives​

Delivery follows the Standard Webhooks standard:

POST https://monitoring.example.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": "Supplier registration" },
"dataset": { "id": 40, "name": "Branch 01" },
"group": { "id": 3, "name": "Operation", "purpose": "operation" },
"cycle": { "id": 7, "name": "October" },
"environment": { "id": 2, "name": "Production", "production": true },
"runner": { "id": 9, "name": "rpa-01", "hostname": "rpa-01" }
}
}

The events​

TypeWhenFields in data
run.finishedA run finishedrun (status, end reason, start, end, duration, Correlation ID), automation, dataset, group, cycle, environment, runner
batch.finishedA batch finishedbatch (total, passed, failed, stopped), group, cycle, environment, runner
batch.not_runA schedule did not go outbatch (with the reason), group, cycle, environment, runner
exception.opened, exception.assigned, exception.resolvedAn operation exception opened, was assigned or resolvedexception (kind, status, occurrences, reason, errorSummary, assignee), automation, dataset, group, cycle, environment
runner.offline, runner.onlineA runner went down (after 2 minutes away) or came backrunner (name, owner, hostname, offlineSince, shared)
webhook.testThe test from the screenorganizationId, message

The run's end reason (endReason) is one of: passed, business_error, technical_error, environment_error, stopped, not_dispatched, runner_lost.

The body version (version) only changes if a field is removed or changes meaning. A new field may show up at any time: treat what you do not know as optional.

Checking the signature​

The webhook secret (whsec_...) is shown only once, at registration and on rotation. The signature is the HMAC-SHA256 of webhook-id.webhook-timestamp.body, with the base64 key that comes after whsec_, encoded in base64 and prefixed with v1,. Always check the body exactly as it arrived, before parsing the JSON, and reject a message with a webhook-timestamp that is too old (more than 5 minutes).

import base64
import hashlib
import hmac
import time


def valid_signature(secret: str, headers: dict, body: bytes, tolerance: int = 300) -> bool:
key = base64.b64decode(secret.removeprefix("whsec_"))
message_id = headers["webhook-id"]
timestamp = headers["webhook-timestamp"]
if abs(time.time() - int(timestamp)) > tolerance:
return False

content = f"{message_id}.{timestamp}.".encode() + body
expected = base64.b64encode(hmac.new(key, content, hashlib.sha256).digest()).decode()

for signature in headers["webhook-signature"].split(" "):
version, _, value = signature.partition(",")
if version == "v1" and hmac.compare_digest(value, expected):
return True
return False

The Standard Webhooks libraries (for Python, Node.js, Java, Go, C# and others) do this check.

Delivery and retries​

  • Response: any 2xx within 10 seconds counts as delivered. Answer quickly and process later.
  • Retries: without a 2xx, Proton tries again with increasing waits (30 seconds, 1 minute, 2 minutes... up to 1 hour between attempts), up to 8 attempts.
  • Repeats: the webhook-id is the same in every attempt of the same event. Keep the ids you received and discard the repeated one.
  • Address: https only, at a public address. Redirects are not followed.
  • Follow-up: the History tab of Alerts and webhooks shows each delivery, with the result, the HTTP status and the error, and lets you resend the one that failed. The test button sends a webhook.test.