- Overview
- API Resources
Webhook event reference
Request headers, CloudEvents body, rule outcomes, size limits, retry schedule, and endpoint requirements for the data quality webhook channel.
This page specifies what an endpoint registered as a webhook alert channel receives: the HTTP request, the event body, how rule results map to outcomes, the limits applied to large runs, and how deliveries are retried.
Request
Each delivery is a POST with a JSON body. Header names are case-insensitive and are sent in lowercase.
POST /hooks/peak HTTP/1.1
content-type: application/json
user-agent: Peak-Webhooks/1.0
webhook-id: evt_1f0c6b2a9d3e4c5f8a7b6c5d4e3f2a1b
webhook-timestamp: 1791191520
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
x-peak-event-type: ai.peak.data_validation.run.completed
x-env: prod
POST /hooks/peak HTTP/1.1
content-type: application/json
user-agent: Peak-Webhooks/1.0
webhook-id: evt_1f0c6b2a9d3e4c5f8a7b6c5d4e3f2a1b
webhook-timestamp: 1791191520
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
x-peak-event-type: ai.peak.data_validation.run.completed
x-env: prod
| Header | Sent | Value |
|---|---|---|
content-type | Always | application/json |
user-agent | Always | Peak-Webhooks/1.0 |
webhook-id | Always | The event id. Identical on every retry of the same event, so it is the key to deduplicate on. |
webhook-timestamp | Always | Unix time in seconds at which this attempt was sent. Fresh on every retry. |
webhook-signature | auth.type is hmac | v1, followed by the base64 HMAC-SHA256 of <webhook-id>.<webhook-timestamp>.<body>. Recomputed on every retry. See Verify the signature. |
x-peak-event-type | Always | The event type, for routing before the body is parsed. |
authorization | auth.type is bearer-token or basic | Bearer <token>, or Basic <base64 of username:password>. |
<headerName> | auth.type is api-key | The key, in the header named by auth.headerName (default X-API-Key). |
| Static headers | When the channel defines headers | The configured names and values, as given. |
Event body
The body is a CloudEvents 1.0 envelope in structured JSON mode. One event is sent per validation run per channel. A run covers one or more tables of a solution, and the event carries the rules of that run which name the channel, narrowed to the outcomes the channel subscribes to, nested under the table each rule checked.
In this example the run covered three tables of orders-pipeline. Three rules name the channel, which subscribes to every outcome: on stage.orders the freshness check failed and the missing-data check produced a warning, and on stage.order_items the foreign-key check passed. No rule on stage.customers names the channel, so that table appears in run.tables only:
{
"specversion": "1.0",
"id": "evt_1f0c6b2a9d3e4c5f8a7b6c5d4e3f2a1b",
"type": "ai.peak.data_validation.run.completed",
"source": "/peak/prod/data-validation/acme",
"subject": "orders-pipeline",
"time": "2026-10-14T09:12:00.123Z",
"datacontenttype": "application/json",
"data": {
"tenant": { "name": "acme", "id": "1234" },
"run": {
"id": "e3f1c2a4-9b77-4d21-8c5e-1a2b3c4d5e6f",
"tables": ["stage.orders", "stage.order_items", "stage.customers"]
},
"solution": "orders-pipeline",
"warehouseType": "snowflake",
"outcome": "failed",
"matched": [
"ai.peak.data_validation.rule.failed",
"ai.peak.data_validation.rule.passed",
"ai.peak.data_validation.rule.warned"
],
"summary": { "total": 3, "errored": 0, "failed": 1, "warned": 1, "passed": 1, "skipped": 0 },
"tables": [
{
"table": "stage.orders",
"outcome": "failed",
"summary": { "total": 2, "errored": 0, "failed": 1, "warned": 1, "passed": 0, "skipped": 0 },
"results": [
{
"rule": {
"type": "freshness",
"severity": "error",
"column": "order_date",
"params": { "column": "order_date", "threshold": 24, "unit": "hours" }
},
"outcome": "failed",
"status": "failed",
"message": "Data is stale: column 'order_date' is 50 hours old (threshold: 24 hours)",
"observed": 50,
"errorCodes": ["DI_E_24F01"],
"details": {
"last_seen": "2026-10-12 07:12:44.000",
"threshold": { "value": 24, "unit": "hours" }
}
},
{
"rule": {
"type": "missing_data_validation",
"severity": "warn",
"column": "order_date",
"params": { "column": "order_date", "interval": 1, "unit": "days", "minRowCount": 100 }
},
"outcome": "warned",
"status": "failed",
"message": "Missing data: 2 period(s) of 1 days had fewer than 100 rows",
"observed": 2,
"errorCodes": ["DI_W_24M01"],
"details": {
"expected_min": 100,
"slice": { "interval": 1, "unit": "days" },
"window": { "start": "2026-09-01 00:00:00.000", "end": "2026-10-14 09:11:58.410" },
"missing_periods": [
{ "start": "2026-10-12 00:00:00.000", "end": "2026-10-13 00:00:00.000", "actual_count": 41 },
{ "start": "2026-10-13 00:00:00.000", "end": "2026-10-14 00:00:00.000", "actual_count": 0 }
]
}
}
],
"resultsTotal": 2,
"resultsTruncated": false
},
{
"table": "stage.order_items",
"outcome": "passed",
"summary": { "total": 1, "errored": 0, "failed": 0, "warned": 0, "passed": 1, "skipped": 0 },
"results": [
{
"rule": { "type": "async_foreign_key", "severity": "warn" },
"outcome": "passed",
"status": "passed"
}
],
"resultsTotal": 1,
"resultsTruncated": false
}
],
"resultsTotal": 3,
"resultsTruncated": false,
"links": {
"dashboard": "https://platform.peak.ai/canvas/assets",
"run": "https://service.peak.ai/validation-api/api/v1/validations/run/e3f1c2a4-9b77-4d21-8c5e-1a2b3c4d5e6f",
"docs": "https://docs.uipath.com/industry-department-solutions/automation-cloud/latest/supply-chain-retail-api-guide/webhook-event-reference"
}
}
}
{
"specversion": "1.0",
"id": "evt_1f0c6b2a9d3e4c5f8a7b6c5d4e3f2a1b",
"type": "ai.peak.data_validation.run.completed",
"source": "/peak/prod/data-validation/acme",
"subject": "orders-pipeline",
"time": "2026-10-14T09:12:00.123Z",
"datacontenttype": "application/json",
"data": {
"tenant": { "name": "acme", "id": "1234" },
"run": {
"id": "e3f1c2a4-9b77-4d21-8c5e-1a2b3c4d5e6f",
"tables": ["stage.orders", "stage.order_items", "stage.customers"]
},
"solution": "orders-pipeline",
"warehouseType": "snowflake",
"outcome": "failed",
"matched": [
"ai.peak.data_validation.rule.failed",
"ai.peak.data_validation.rule.passed",
"ai.peak.data_validation.rule.warned"
],
"summary": { "total": 3, "errored": 0, "failed": 1, "warned": 1, "passed": 1, "skipped": 0 },
"tables": [
{
"table": "stage.orders",
"outcome": "failed",
"summary": { "total": 2, "errored": 0, "failed": 1, "warned": 1, "passed": 0, "skipped": 0 },
"results": [
{
"rule": {
"type": "freshness",
"severity": "error",
"column": "order_date",
"params": { "column": "order_date", "threshold": 24, "unit": "hours" }
},
"outcome": "failed",
"status": "failed",
"message": "Data is stale: column 'order_date' is 50 hours old (threshold: 24 hours)",
"observed": 50,
"errorCodes": ["DI_E_24F01"],
"details": {
"last_seen": "2026-10-12 07:12:44.000",
"threshold": { "value": 24, "unit": "hours" }
}
},
{
"rule": {
"type": "missing_data_validation",
"severity": "warn",
"column": "order_date",
"params": { "column": "order_date", "interval": 1, "unit": "days", "minRowCount": 100 }
},
"outcome": "warned",
"status": "failed",
"message": "Missing data: 2 period(s) of 1 days had fewer than 100 rows",
"observed": 2,
"errorCodes": ["DI_W_24M01"],
"details": {
"expected_min": 100,
"slice": { "interval": 1, "unit": "days" },
"window": { "start": "2026-09-01 00:00:00.000", "end": "2026-10-14 09:11:58.410" },
"missing_periods": [
{ "start": "2026-10-12 00:00:00.000", "end": "2026-10-13 00:00:00.000", "actual_count": 41 },
{ "start": "2026-10-13 00:00:00.000", "end": "2026-10-14 00:00:00.000", "actual_count": 0 }
]
}
}
],
"resultsTotal": 2,
"resultsTruncated": false
},
{
"table": "stage.order_items",
"outcome": "passed",
"summary": { "total": 1, "errored": 0, "failed": 0, "warned": 0, "passed": 1, "skipped": 0 },
"results": [
{
"rule": { "type": "async_foreign_key", "severity": "warn" },
"outcome": "passed",
"status": "passed"
}
],
"resultsTotal": 1,
"resultsTruncated": false
}
],
"resultsTotal": 3,
"resultsTruncated": false,
"links": {
"dashboard": "https://platform.peak.ai/canvas/assets",
"run": "https://service.peak.ai/validation-api/api/v1/validations/run/e3f1c2a4-9b77-4d21-8c5e-1a2b3c4d5e6f",
"docs": "https://docs.uipath.com/industry-department-solutions/automation-cloud/latest/supply-chain-retail-api-guide/webhook-event-reference"
}
}
}
Envelope
| Field | Description |
|---|---|
specversion | Always 1.0. |
id | Unique per event: evt_ followed by 32 hexadecimal characters. Equals the webhook-id header. Two channels notified for the same run receive different ids. |
type | Always ai.peak.data_validation.run.completed. What happened is in data.outcome and data.summary. |
source | /peak/<environment>/data-validation/<tenant>. <environment> is prod for production, so an endpoint fed by more than one environment can tell them apart. |
subject | <solution>, the solution the run validated. The tables are listed in data.run.tables. |
time | When the run completed, in RFC 3339 format, UTC, with millisecond precision. |
datacontenttype | Always application/json. |
data
| Field | Description |
|---|---|
tenant.name, tenant.id | The tenant the run belongs to. id is a string. |
run.id | The validation run. It is the same in every event sent for that run, whichever channel receives it, so it correlates them. The full report is available from Check a validation run. |
run.tables | Every table the run covered, schema-qualified, in request order, including tables with no result in this event. Compare it with tables to tell a channel that saw part of a run from a run over one table. |
solution, warehouseType | The solution the run validated and its warehouse, snowflake or redshift. |
outcome | The worst outcome among the carried results across every table, in the order errored, failed, warned, passed. |
matched | The channel's subscribed outcomes that are present in the carried results, as ai.peak.data_validation.rule.<outcome> names, sorted. This is why the channel was notified. |
summary | Counts of the carried results across every table by outcome: total, errored, failed, warned, passed, skipped. Complete even when results are capped. Skipped rules are never carried, so skipped is 0. |
tables | One entry per table with a carried result, in the order of run.tables. See Table entry. |
resultsTotal, resultsTruncated | The number of carried results across every table, and whether any table's results holds fewer than its resultsTotal. |
links.dashboard, links.run, links.docs | The Canvas asset list where the Data Quality Dashboard lives, the run report endpoint, and this documentation. For a spoke tenant, links.run is on the spoke's service host. |
Table entry
| Field | Description |
|---|---|
table | The schema-qualified table, as tables in the run report names it. A foreign-key result is filed under the child table. |
outcome | The worst outcome among this table's carried results, in the same order as data.outcome. |
summary | Counts of this table's carried results by outcome, with the same keys as data.summary. The tables' counts add up to data.summary. |
results | This table's carried results, ordered errored, failed, warned, passed. The cap applies across the whole event, see Size limits. |
resultsTotal, resultsTruncated | The number of this table's carried results, and whether results holds fewer than that. |
Result entry
| Field | Present | Description |
|---|---|---|
rule.type | Always | freshness, missing_data_validation, or async_foreign_key. |
rule.severity | Always | error or warn, from the validation entry. |
rule.column | When the rule measures a column | The column, for freshness and missing_data_validation. |
rule.params | When the rule has parameters | The parameters the rule was evaluated with. |
outcome | Always | failed, warned, errored, or passed. See Rule outcomes. |
status | Always | The raw run status: passed, failed, or error. |
message | Problems only | The human-readable result, the same text a Slack or email alert shows. For an errored result, why the rule was not evaluated. |
observed | Problems, when measured | The measurement: the age of the newest record for freshness, the number of short periods for missing_data_validation, the number of orphan rows for async_foreign_key. |
errorCodes | Problems only | The DI_E_ or DI_W_ codes of the result. See Results. |
details | Problems, when available | Rule-specific evidence: last_seen and threshold for freshness; expected_min, slice, window, and missing_periods for missing data; constraints and totalOrphanRows for foreign keys. Lists are sampled, see Size limits. |
detailsOmitted | When details was dropped for size | true when a rule's details exceeded 2 KB and were left out. |
Passing results carry only rule, outcome, and status, so a receiver sees what was checked without the evidence a problem needs.
Rule outcomes
A result's outcome is its status read through its severity. The outcome names, prefixed ai.peak.data_validation.rule., are the values a channel's events list accepts.
status | severity | Outcome | Subscription value |
|---|---|---|---|
failed | error | failed | ai.peak.data_validation.rule.failed |
failed | warn | warned | ai.peak.data_validation.rule.warned |
error | any | errored | ai.peak.data_validation.rule.errored |
passed | any | passed | ai.peak.data_validation.rule.passed |
skipped | any | skipped | None. Skipped rules are never delivered. |
The names mean the following:
failed: the check ran and failed, and the rule hasseverity: error.warned: the check ran and failed, and the rule hasseverity: warn.errored: the rule could not be evaluated. Every rule of a table whose validation timed out or whose configuration is invalid ends this way;messagegives the reason, and the service logs have the rest.passed: the check held.
A channel's event carries the rules that name the channel and whose outcome is in its events list, which defaults to all four for a webhook. A run in which none of the carried rules has a listed outcome produces no event. A run whose carried rules all passed has outcome: passed and reaches only channels subscribed to ai.peak.data_validation.rule.passed.
To gate a pipeline on a run, subscribe to all four outcomes. A channel that subscribes to a subset sees only that subset, so a channel subscribed to ai.peak.data_validation.rule.passed alone receives outcome: passed for a run that failed on checks it does not carry.
Size limits
| Limit | Behavior |
|---|---|
| Results per event | At most 100 across all tables. Beyond that, the 100 worst results in outcome order are kept and placed under their tables; data.resultsTotal has the full count and data.resultsTruncated is true. A table whose results were all cut still appears with its outcome, summary, and resultsTotal, with results empty and resultsTruncated: true. |
Lists inside details (missing_periods, missing_dates) | Longer than 20 items, the last 20 are kept, with <key>_total and <key>_truncated: true alongside. |
details per result | Larger than 2 KB after sampling, the details are replaced by detailsOmitted: true. |
| Whole body | About 60 KB. If a body is larger, details is dropped from every result; if it is still too large, the results cap is halved repeatedly, down to none. Every table stays listed, and summary and resultsTotal stay complete at both levels. |
Delivery and retries
An event is handed to Peak's delivery service when the run completes. At that point the run report lists the channel in alerts_sent, which records that the event was accepted for delivery, not that your endpoint received it.
| Aspect | Behavior |
|---|---|
| Request | POST, with the body serialised once. The endpoint must respond within 10 seconds (5 seconds for the verification test event). The response body is ignored. |
| Success | Any 2xx status. |
| Redirects | Never followed. A 3xx is a permanent failure. |
| Retried | 408, 425, 429, any 5xx, a timeout, and connection or TLS failures. Six attempts in total, the retries after 10 seconds, 1 minute, 5 minutes, 30 minutes, and 2 hours. A Retry-After header on a 429 or 5xx response lengthens the wait, within 5 seconds to 1 hour. After the sixth failed attempt the event is dropped. |
| Not retried | 3xx, 410, 401 or 403 when a credential was sent, any other 4xx, a hostname that does not resolve, a URL that fails the endpoint requirements, and a credential that is missing, expired, or in the wrong format. Fix the cause; the next run delivers normally. |
| Retries | Carry the same webhook-id and body, with a new webhook-timestamp and signature. Delivery is at-least-once, so deduplicate on webhook-id. |
| Order | Not guaranteed. A retried older event can arrive after a newer one; time and data.run.id tell them apart. |
| Source addresses | Deliveries originate from the Peak platform. If your endpoint restricts callers by IP address, contact support for the addresses to allow. |
Endpoint requirements
https, on port 443. An explicit:443is accepted.- A public, fully qualified hostname. IP addresses,
localhost, single-label names, and names ending in.local,.localhost,.internal,.svc, or.cluster.localare rejected when the channel is registered. - Every address the hostname resolves to must be public. A name that resolves to a private, loopback, or link-local address is refused at send time with the reason
egress_blocked, and the check is repeated on every delivery. - TLS 1.2 or later, with a certificate issued by a public certificate authority. A self-signed certificate fails as a
networkerror. - No embedded credentials and no fragment in the URL. A query string is allowed.
- A
2xxresponse within the timeout. Accept the event, then process it; a slow handler is retried as atimeout, and the retries repeat the same event.
Verification test event
Registering, updating, or verifying a channel sends one ai.peak.webhook.ping event to the URL, with the same headers and signature as a delivery and x-peak-event-type: ai.peak.webhook.ping. The channel is stored only if the endpoint answers 2xx within 5 seconds.
{
"specversion": "1.0",
"id": "evt_7c1b9e7a-5d2f-4a1e-9b3c-2f6d8e0a4c11",
"type": "ai.peak.webhook.ping",
"source": "/peak/prod/notifications/acme",
"subject": "alerts.example.com",
"time": "2026-10-05T09:11:58.410Z",
"datacontenttype": "application/json",
"data": {
"message": "Test event sent to verify this webhook destination. No action is required.",
"tenant": { "id": "1234", "name": "acme" }
}
}
{
"specversion": "1.0",
"id": "evt_7c1b9e7a-5d2f-4a1e-9b3c-2f6d8e0a4c11",
"type": "ai.peak.webhook.ping",
"source": "/peak/prod/notifications/acme",
"subject": "alerts.example.com",
"time": "2026-10-05T09:11:58.410Z",
"datacontenttype": "application/json",
"data": {
"message": "Test event sent to verify this webhook destination. No action is required.",
"tenant": { "id": "1234", "name": "acme" }
}
}
Compatibility
Fields are only ever added within an event type, so a receiver must ignore fields and enumeration values it does not recognise. id, type, source, subject, data.run.id, data.outcome, and data.summary keep their meaning. A breaking change ships as a new type.