Add generic webhook notifier for alerts and reports#9643
Open
dfliess wants to merge 10 commits into
Open
Conversation
(cherry picked from commit 87406d7854fd20684392e467c8f2af021763c8d1)
Inert until the Spanish locale is registered in the inlang project settings (pending in rilldata#9635); included here so the two PRs compose cleanly in either merge order.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Alerts and scheduled reports can currently notify via email and Slack only. Integrating
with anything else — PagerDuty, Opsgenie, n8n/Zapier/Make flows, or an in-house system —
requires abusing Slack incoming webhooks or polling the API.
This PR adds a generic
webhooknotifier driver, completing the connector-agnosticNotifiers {connector, properties}design introduced in #4371. Because that groundworkexists, the change is contained: no runtime proto changes and no new Go dependencies
(delivery uses
hashicorp/go-retryablehttp, already a direct dependency; signing is stdlibcrypto/hmac). The only proto change is two additivewebhook_urlsfields on the adminAPI's
AlertOptions/ReportOptionsfor the UI dialogs.YAML surface (alerts and reports)
Parity with the
slackblock: one entry appended toNotifiers, multiplicity insideproperties. The optional
connector:key selects a named connector instance (thereconciler already resolves notifiers by connector name), enabling per-receiver
secrets/headers without new machinery.
Connector config (via
connector.<name>.*variables or a connector YAML, never in projectfiles):
signing_secret(secret) andheaders(map, e.g. anAuthorizationheader forreceivers behind an API gateway — same pattern as the
httpsdriver).Payload
Versioned envelope, two event types mirroring the
Notifierinterface:{ "id": "unique-per-delivery", "type": "alert.status", // or "report.scheduled" "version": 1, "timestamp": "2026-07-02T15:04:05Z", "data": { "display_name": "…", "execution_time": "…", "status": "PASS | FAIL | ERROR", "is_recover": false, "fail_row": { "…": "…" }, "execution_error": "", "open_link": "…", "edit_link": "…" } }report.scheduledcarriesdisplay_name,report_time,download_format,summary,open_linkanddownload_link.fail_rowis included for parity with the email/Slacknotifiers;
ToEmail/ToNameare excluded (email-specific, per the #4371 review), and nounsubscribe link is sent (webhook receivers are anonymous).
Signing: Standard Webhooks
Signatures follow the Standard Webhooks spec exactly
(
webhook-id/webhook-timestamp/webhook-signatureheaders, HMAC-SHA256 over{id}.{timestamp}.{body},whsec_secret format), so receivers can verify with theexisting libraries in 11+ languages. Implemented with ~15 lines of stdlib code and
unit-tested against the spec's official test vectors. Without a
signing_secrettherequest is sent unsigned (capability-URL receivers like Zapier/n8n — same spirit as Slack
incoming webhooks working without a bot token). The connector's
Pingvalidates that thesecret is well-formed so misconfigurations surface early.
Delivery semantics
go-retryablehttp: 3 attempts, exponential backoff (1s → 4s), 10s per-attempt timeout;retries on 5xx/429/network errors, hard-fails on other 4xx. Success = any 2xx.
errors.Joinand surface asthe execution's
ERRORresult. Since executions are the only delivery-visibilitysurface, each per-URL error is self-sufficient (URL + status + attempts).
scope for a notifier driver. The stable
idlets receivers deduplicate.UI
Mirrors the Slack pattern: a "Webhook notifications" section (toggle + URL multi-input) in
the alert and report create/edit dialogs, gated on
ListNotifierConnectorsreporting aconfigured
webhookconnector (with a "not configured" docs hint otherwise), and thedestinations listed on the alert/report detail pages. The
connector:key stays YAML-only,like named Slack instances today. New strings use paraglide messages in
en.json; Spanishtranslations ship as an inert
es.jsonso this composes cleanly with #9635 in eithermerge order.
Drive-by fix
popCurrentExecutionin the alert reconciler dereferencedadminMetaunguarded in thenon-email notifier path — with an admin service that doesn't implement alert metadata
(e.g. the test runtime's noop admin, or local development), any non-email notifier
(including Slack today) panics. The email path already guards this; the fix applies the
same guard, sending the notification without open/edit links, matching the documented
intent.
Tested
Unit tests for the driver (signing vectors, retry policy, per-URL error aggregation,
dedupe, unsigned mode, Ping) and the parser (alerts, reports, URL validation, default and
named connectors). Exercised end-to-end on our self-hosted deployment: alert and report
created through the dialogs,
alert.statusandreport.scheduled(ad hoc and cron)delivered and signature-verified against a local receiver.
Open questions
exists via
notify.slack.webhooks, so this is not a regression, but: would you want anoptional
allowed_url_prefixesallowlist (precedent:path_prefixesin the httpsdriver) or a private-IP guard in v1?
or would you prefer single-attempt (Slack parity)?
docs/docs/developers/build/connectors/services/webhook.md; shouldthe payload JSON schema be published more formally?