Webhook Notifications#
oxo-flow includes a webhook client (oxo_flow_core::webhook, behind the webhook cargo feature — enabled by default) that can send JSON notifications about workflow and rule execution events to external services like Slack, Microsoft Teams, Discord, or any custom HTTP endpoint.
Status note:
oxo-flow rundispatches the workflow-level events —workflow_startedat the beginning andworkflow_completed/workflow_failedat the end — whenever a[webhook]table is present. Dispatch is best-effort: a failing endpoint logs a warning and never changes the run status. The rule-level events (rule_completed,rule_failed) are defined in the config model but not yet dispatched during execution; subscribing to them currently has no effect.
Configuration#
A webhook is described by the WebhookConfig struct:
| Field | Type | Default | Description |
|---|---|---|---|
url |
String | — (required) | Webhook endpoint URL. |
method |
String | "post" |
HTTP method: post, put, or get. |
headers |
Map | {} |
Custom HTTP headers to include in the request. |
events |
Array of String | ["workflow_completed"] |
Events to subscribe to (see below). If omitted, only workflow_completed is used. |
secret |
String | none | Secret key for HMAC-SHA256 signature. |
timeout_secs |
Integer | 30 |
Request timeout in seconds. |
max_retries |
Integer | 3 |
Maximum retries on failure (with exponential backoff: 1s, 2s, 4s...). |
Example Configuration#
url = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
events = ["workflow_started", "workflow_completed", "workflow_failed"]
method = "post"
headers = { "X-Custom" = "value" }
secret = "my-shared-secret" # Used for HMAC-SHA256 signature
timeout_secs = 30
max_retries = 3
Supported Events#
Events are serialized in snake_case. You can subscribe by listing them in the events array:
| Event Name | Description |
|---|---|
workflow_started |
Fired when the workflow execution begins. |
workflow_completed |
Fired when the workflow finishes successfully. |
workflow_failed |
Fired when the workflow fails. |
rule_completed |
Not yet dispatched — defined for future rule-level notifications. |
rule_failed |
Not yet dispatched — defined for future rule-level notifications. |
There is no RuleStarted or RuleSkipped event in the webhook module. (The
ExecutionEvent log stream in the core executor likewise has no
workflow_cancelled variant — an interruption surfaces as rule_failed
records with an interrupted by <signal> skip reason.)
If the events array is omitted, the default subscription is ["workflow_completed"] only.
Payload#
The json payload is a standard HTTP POST request with Content-Type: application/json. The body is a WebhookPayload:
{
"event": "rule_failed",
"workflow_name": "variant-calling",
"timestamp": "2026-05-18T12:00:00Z",
"data": {
"total_rules": 12,
"succeeded": 10,
"failed": 1,
"skipped": 1,
"duration_ms": 45210,
"rule": "align_reads",
"exit_code": 1,
"error": "bwa: command not found"
},
"version": "0.23.2"
}
Field details:
event— the event name in snake_case.workflow_name— the workflow name.timestamp— ISO 8601 timestamp.data— event-specific fields; only the fields relevant to the event are present (total_rules,succeeded,failed,skipped,duration_ms,rule,exit_code,error).version— the oxo-flow version that sent the notification.
Slack format#
For Slack-compatible endpoints, WebhookPayload::to_slack_payload() converts the payload to a {"text": "...", "blocks": [...]} message with per-event status emoji (green success, red failure), directly compatible with Slack Incoming Webhooks.
Security (HMAC Signatures)#
If you set the secret field, oxo-flow computes an RFC-2104 HMAC-SHA256
signature over the payload body and includes it in the
X-OxoFlow-Signature HTTP header (verified against the published RFC 4231
test vectors):
POST /alerts HTTP/1.1
Host: api.my-monitoring.com
Content-Type: application/json
X-OxoFlow-Signature: hmac-sha256=abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890
{"event": "rule_failed", ...}
Your receiving endpoint can use this signature to verify that the webhook request genuinely originated from your oxo-flow execution and that the payload was not tampered with in transit.
Redirects are not followed. Webhook URLs typically embed a credential
(a Slack/Discord incoming-webhook token sits in the path), and the request
headers are bound to that one endpoint — the X-OxoFlow-Signature HMAC and
any custom headers you configured. oxo-flow therefore never replays a
request to a redirect target: a 3xx response is reported as a
non-success status, and request errors never echo the configured URL
(the credential) in their messages. If your endpoint moves, configure the
final URL directly.
Legacy scheme. The original implementation signed with a non-standard
keyed SHA-256 (sha256=hex(sha256(secret‖body))), which is not
HMAC-SHA256 despite the header name. It remains available via
signature_scheme = "sha256-keyed" for existing consumers and is frozen:
emit a warning, switch your verifier to hmac-sha256, and it will be
removed in a future major version. The default is "hmac-sha256".