Skip to main content

Webhooks

Trinitite can push event payloads to your endpoints as things happen — verdicts land, training jobs finish, policies are finalized, governance controls trip. Webhooks are the lowest-latency way to react to the platform without polling.

Register an endpoint

POST /v1/webhooks
Authorization: Bearer <api_key>
Content-Type: application/json

{
"url": "https://your.app/webhooks/trinitite",
"events": ["verdict.created", "training.job.completed", "policy.finalized", "governance.shutdown_engaged"],
"secret": "whsec_..."
}
FieldDescription
urlHTTPS endpoint on your side. Must respond 2xx within 10 s.
eventsList of event types to subscribe to (see § Event types).
secretUsed to sign payloads. Rotatable; never reuse across endpoints.

The platform returns a webhook record with an id (wh_01J…) and the active secret hash. Manage endpoints via GET / PATCH / DELETE /v1/webhooks/:id.

Event types

EventWhen it firesPayload root
verdict.createdEvery governance verdict is written to the ledger.verdict
training.job.completedA training/LoRA job reaches a terminal state.job
policy.finalizedA policy document is locked.policy
governance.shutdown_engagedAn emergency kill-switch is engaged.control
governance.breaker_trippedA circuit breaker trips.control
attestation.readyAn attestation report finishes generating.attestation
eval.run.completedAn Eval run finishes.eval_run

Payload shape

Every delivery uses one envelope:

{
"id": "evt_01J9X…",
"type": "verdict.created",
"created_at": "2026-05-01T12:00:00Z",
"data": {
"verdict": "corrected",
"guardian": "pii-redactor",
"ledger_id": "led_01J9X…",
"policy_hash": "sha256:7f3a…",
"correction_diff": [
{ "op": "replace", "path": "/choices/0/message/content", "value": "Customer SSN: [REDACTED]" }
]
},
"previous_attempt": null,
"attempt": 1
}

The data object mirrors the resource documented on the relevant endpoint page — verdict.created matches the verdict vocabulary receipt, training.job.completed matches the Training job shape, and so on.

Signature verification

Every delivery is signed with your endpoint's secret using HMAC-SHA256. Two headers travel with the POST:

HeaderDescription
X-Trinitite-Signaturet=<unix_ts>,v1=<hex_hmac>
X-Trinitite-Event-IdThe evt_… id (for idempotency on your side).

Verify on your end:

import hmac, hashlib, time

def verify(payload_body: bytes, sig_header: str, secret: str, tolerance_sec: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in sig_header.split(","))
t, v1 = parts["t"], parts["v1"]
if abs(time.time() - int(t)) > tolerance_sec:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + payload_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(v1, expected)

Reject any delivery whose signature fails or whose timestamp is outside your replay tolerance.

Retries & idempotency

  • The platform considers a delivery successful when your endpoint returns any 2xx status. Anything else (including timeouts and 3xx) triggers a retry.
  • Retry backoff: exponential, up to 24 hours, then the delivery is marked failed and is available via GET /v1/webhooks/:id/events.
  • Redeliveries carry the same X-Trinitite-Event-Id and the same data — deduplicate on the event id on your side.
  • previous_attempt is set on redeliveries so you can distinguish a fresh event from a retry.

Ordering

Events for a single resource are delivered in order. Events across different resources are not strictly ordered — rely on created_at and the resource's own version/sequence fields, not arrival order, for cross-resource correlation.


Verdict vocabulary — the shape of the verdict.created payload. → Errors, rate limits & conventions — idempotency and retry semantics. → Logs — pull historical verdicts when a webhook was missed.