Webhook event reference
Rule-line delivers a terminal webhook event to a job's resolved destination after a run reaches
run.succeeded or run.failed. Queued/active run state is never delivered — poll GET /jobs/{id} for that. An API-triggered test run (test: true) sends the SAME event shape,
with test: true set and no business-data mutation.
Event body
| Field | Meaning |
|---|---|
eventId | The stable identity of this terminal event. The SAME value across every automatic and manual delivery attempt — use it for deduplication. Never the run ID. |
runId | The run this event reports the terminal outcome of. |
eventType | Either run.succeeded or run.failed. Rule-line emits exactly these two terminal event types; queued/active run state is a polling-only concern and is never delivered. |
eventCreatedAt | The immutable instant this event was created, at the run's first terminalization. Never changes across retries. |
runCompletedAt | The immutable instant the underlying run reached its terminal outcome. Never changes across retries. |
rulesetId | The id of the ruleset version actually executed, selected through the active version at submission. |
rulesetVersion | The exact ruleset version this run executed. |
requestedRulesetId | The activated version handle the caller named. It can differ from rulesetId after an approval or rollback; null when the run has no requested-version attribution. Immutable events created before this field was introduced can omit it. Poll responses call this member requestedRulesetVersionId. |
locale | The run language frozen at submission: de for German or en for English. An omitted job locale uses the tenant default at submission; later settings changes do not change this event. |
test | True when the originating job was submitted with test: true. A test run makes no business-data mutations, but still sends the same terminal event shape. |
outcome | Either succeeded or failed — the run's immutable terminal outcome. |
result | The ruleset's declared output on success, or the stable structured failure envelope on failure. Submitted JSON input and document content are never included. A large result is represented as a stable authenticated result-resource reference (href, sha256, sizeBytes) rather than being inlined. |
Example values
Illustrative full-length values for the identifier/URL/digest-shaped fields above — long enough to exercise this reference's own overflow and wrapping handling (UI-17/UI-18) in a real table, not just in prose.
| Field | Example |
|---|---|
eventId | 018f2c1a-9e3b-7c9a-8b0e-4f2a6d1c5e7b |
destinationUrlUsed | https://integrations.example-customer-tenant.example.com/webhooks/rule-line/incoming-terminal-events |
result.sha256 | 4f8b2c9a1d6e3f705c8a2b9d1e4f6a3c8b0d2e5f7a9c1b3d6e8f0a2c4b6d8e0f |
Rule-Line-Signature header | t=1780012345,v1=c1a2b3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90,v1=90f8e7d6c5b4a392817069584736251403f2e1d0c9b8a796857463524130f2e |
Signing
Every delivery attempt is signed with HMAC-SHA256 over ${timestamp}.${exactBodyBytes}
(the raw, byte-exact request body — never a re-serialized copy) and sent in the
Rule-Line-Signature header as t=<unix-seconds>,v1=<hex>.
During a destination secret rotation's overlap window, the header carries BOTH signatures —
the CURRENT (active) secret's signature always first, then the previous secret's:
t=<unix-seconds>,v1=<activeHex>,v1=<previousHex>. A receiver holding either secret can
verify the delivery; accept a match against ANY v1 value present, not only the first.
Delivery outcome and retries
Any HTTP response in the 2xx range acknowledges delivery. A redirect, a timeout, a
transport-level error, and every non-2xx status are all treated as an unsuccessful attempt
and retried — including a 4xx response.
One automatic delivery cycle makes up to 10 total attempts over roughly 72 hours, with exponential backoff and jitter:
| Attempt | Nominal delay since previous attempt | Worst case (with jitter) |
|---|---|---|
| 2 | ~300s | up to ~345s |
| 3 | ~900s | up to ~1035s |
| 4 | ~2700s | up to ~3105s |
| 5 | ~8100s | up to ~9315s |
| 6 | ~24300s | up to ~27945s |
| 7 | ~43200s | up to ~49680s |
| 8 | ~43200s | up to ~49680s |
| 9 | ~43200s | up to ~49680s |
| 10 | ~43200s | up to ~49680s |
Manual retry
After a delivery cycle exhausts its attempts, an authorized user can manually retry it. A
manual retry starts a fresh 10-attempt cycle for the SAME
immutable event — it redelivers the exact same event body to the destination's CURRENT URL
using its CURRENT active signing secret. The eventId never changes, and the underlying job
is never resubmitted or re-evaluated: a retry is a delivery operation only, never a run
operation. Contrast this with submitting an identical new POST /jobs request, which always
creates and meters an independent second run.
Delivery attempt fields
Every attempt in GET /runs/{runId}/delivery's attempts array:
| Field | Presence | Type |
|---|---|---|
attemptLabel | required | string |
attemptNumber | required | integer |
cycleNumber | required | integer |
destinationUrlUsed | required | string or null |
httpStatus | required | integer or null |
initiatedBy | required | "system" | "manual" |
latencyMs | required | integer or null |
nextAttemptAt | required | string or null |
requestPreview | required | string or null |
responsePreview | required | string or null |
secretVersionUsed | required | string (uuid) or null |
sentAt | required | string |
status | required | "due" | "claimed" | "succeeded" | "failed" | "exhausted" |
transportError | required | string or null |