Skip to content
datasquares.aiSign inStart free

Metrics & Governance

Webhooks

Webhooks enqueue outbound notifications when DataSquares events happen, so your receiver does not poll. Delivery is asynchronous and best-effort: the product action has already succeeded if enqueueing or delivery later fails. Point one at Slack for readable messages or at your own endpoint for automation.

Kind Payload
generic structured JSON — for your own services and automation
slack a ready-to-post Slack incoming-webhook message (text + blocks, markdown sections with a compact field list) — paste a Slack webhook URL and events render readably in the channel, no middleware

Each webhook subscribes to the events it cares about. The ones you’ll use first:

Event Fires when
alert.fired a metric alert crosses its threshold, when the alert’s creator administers the workspace (below)
digest.sent a metric digest that a workspace admin made runs — Slack hooks get one readable line per metric, with pace status
source.sync_failed a scheduled source refresh fails
metric.anomaly_detected a certified metric’s scheduled anomaly sweep finds an unusual movement, when the metric’s creator administers the workspace (below)
model.created / model.published a data model is created or published
dashboard.created / dashboard.published / dashboard.deleted a dashboard changes lifecycle state
report.deleted a report is deleted
dashboard.restored / report.restored / story.restored / presentation.restored / metric.restored a deleted item is restored from the Recovery Center
report.delivered a scheduled report delivery completes
pipeline.run_failed a pipeline run reaches failed state
usage.budget_exceeded product usage crosses its configured budget
access.granted / access.revoked a resource access grant changes
compute.queue_stuck, compute.vm_unreachable, compute.provision_failing compute recovery detects an operational fault — sent only to the workspaces of the people who run the machines (on a self-hosted install, your admins)
webhook.test you press test — verify the pipe before trusting it

A dashboard or report deleted by a person arrives with deletedBy. One the system removed automatically — an untitled draft nobody changed for 7 days, see Drafts nobody touched — arrives with reason: "untouched_draft" and no deletedBy.

Event names are free-form subscriptions rather than a server-validated enum. Subscribing to a name that nothing emits is accepted but produces no delivery.

An alert.fired event is the alert’s own check, read as its creator. Its number is the creator’s, so the workspace’s channels carry it only when that person administers the workspace, as with digests and anomalies. The alert’s email and push tell each person in their own number instead (Alert rules).

Field What it holds
alertId the alert’s id
name the alert’s name
value the number it compared; null when it is withheld
withheld only when the value may not leave DataSquares, why, in words: It reads a sensitivity-labelled field that can’t be shown here, so its value is withheld., or Its value is withheld: DataSquares could not check that it may be sent. when that check fails
threshold its threshold
period for an alert on a metric, the period it compared, as a person names it (October 2026, November 2026 to date, All data); null for one on a field or a log search
valueText the value as written: in the metric’s format for an alert on a metric, grouped otherwise, or the withheld sentence

An alert on a metric’s period is told again for each new period it compares while it stays past its threshold, so one alert can send one event per period.

A metric.anomaly_detected event is the anomaly sweep’s finding for one metric and one period, read as the metric’s creator. Its numbers are the creator’s, so the workspace’s channels carry it only when that person administers the workspace (a company admin, or a member whose role holds the workspace’s admin rights). Anyone else’s finding is told only in the app, to the creator, the metric’s owner and its followers, each in their own numbers. A digest’s digest.sent follows the same rule, for the person who made the digest.

Its data, the sentences first, then the figures for automation:

Field What it holds
summary the finding in words: Revenue for October 2026 is unusually low: 31% under its usual level.
detail the two numbers, in the metric’s format: October 2026: 812,000, against a usual level of 1,180,000.
name the metric’s name
period the period judged, as a person names it (October 2026, Q3 2026, Week of 5 Oct)
direction spike or drop
severity medium, high or critical
bucket the period’s first day (2026-10-01)
metricId the metric’s id
value the period’s value
usual its usual level: the average of the periods before it in the sweep’s window
zScore, driftPct how many standard deviations the value is from the usual level, and how far it is from the median of those periods, as a share of it (0.31 for 31%)

A Slack hook titles the event Unusual change in a metric and shows the first eight of these fields, so the sentences lead.

A metric that reads a sensitivity-labelled field sends why in place of its numbers, whatever its creator’s role: summary (Revenue for October 2026: It reads a sensitivity-labelled field that can’t be shown here, so its value is withheld.), name, period, bucket, metricId and withheld, the sentence alone, with no detail, figures or statistics.

Webhooks are managed through the REST API today (no web UI yet): create a webhook with its URL, kind, and event subscriptions; send a test delivery; and inspect recent deliveries to debug a silent channel.

You can update the name, URL, subscriptions, headers, secret, or enabled state, and delete a webhook. Kind cannot be changed after creation; create a new webhook to switch between generic and Slack formatting. Secrets are used internally and are never returned by list/create responses. A secret is stored encrypted; a webhook whose secret can no longer be decrypted (after the platform’s encryption key changes without it) fails its deliveries with The webhook’s signing secret could not be decrypted with the configured encryption key. Set the secret again., rather than send them unsigned. Without an encryption key on the server, setting a secret is refused (503, The webhook’s secret cannot be stored: no encryption key is configured.). An empty secret removes it.

A webhook sends the workspace’s events to an address outside DataSquares, so managing them is a company admin’s job, as the workspace’s other settings are. Listing, creating, changing, testing and deleting webhooks, and reading their deliveries, all need a company admin signed in (with the admin settings permission, which a company admin has), in the workspace. Anyone else, a member or a viewer, is refused with 403, and so is an API key, whatever its scope: call these endpoints with a signed-in admin’s session token (Authorization: Bearer), as below. A webhook is reachable only from its own workspace; another workspace’s id answers 404.

Members still benefit from them: a webhook delivers the events the product emits for the workspace, and anyone who can share a story can post it to one of the workspace’s Slack webhooks (by name; its address is never shown) with Share to Slack.

Terminal window
curl -X POST https://<your-host>/api/webhooks \
-H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
-d '{
"name": "Operations Slack",
"url": "https://hooks.slack.com/services/T000/B000/XXXX",
"kind": "slack",
"events": ["alert.fired", "source.sync_failed"]
}'

Generic deliveries use this envelope:

{
"event": "alert.fired",
"timestamp": "2026-08-27T10:00:00.000Z",
"data": {}
}

Every attempt includes X-DataSquares-Timestamp and a stable X-DataSquares-Delivery-Id for receiver-side deduplication. If a secret is configured, X-DataSquares-Signature is sha256=<HMAC-SHA256(secret, timestamp + "." + deliveryId + "." + rawBody)>. Reject stale timestamps and compare signatures in constant time. The delivery ID stays the same across retries; the signed timestamp changes per attempt.

Custom headers are merged first. DataSquares then overwrites protected headers such as content type, user agent, delivery ID, timestamp, and signature so a stored custom header cannot forge them.

Each subscribed webhook gets its own delivery record, pending until it is delivered or failed. The retry budget is five attempts: after a failed attempt the next is due 1, 2, 4, then 8 seconds later, and the fifth failure marks it failed. Due deliveries are picked up every 30 seconds, so in practice attempts are about half a minute apart or more. Each outbound request has a configurable timeout (10 seconds by default).

Targets are checked when configured and again at delivery time to resist DNS rebinding. Public deployments reject private, loopback, and cloud-metadata addresses; a self-hosted operator may explicitly allow private targets. Redirects are never followed because the redirect destination has not passed the target check. A blocked target is marked permanently failed.

POST /api/webhooks/{id}/test queues a webhook.test delivery and returns the test envelope; it does not wait for a successful HTTP response. Inspect GET /api/webhooks/{id}/deliveries for newest-first status, attempts, last error, retry time, and delivery time. The list defaults to 50 rows and supports up to 200 per page with an offset.