Who this is for: DevOps engineers configuring SIEM ingestion, GRC platform architects connecting compliance feeds, and security teams building alert pipelines. Requires Enclave tier ($9,500/mo) or above.
1. Overview
SWT3 Regulatory Webhooks push real-time compliance events to your infrastructure. When a verdict is recorded, a control drifts, or hardware attestation goes stale, your webhook endpoint receives an HMAC-signed JSON payload within seconds.
Use webhooks to:
- Feed compliance events into your SIEM (Splunk, Datadog, Elastic)
- Trigger alerts in PagerDuty, Slack, or Microsoft Teams on FAIL verdicts
- Sync evidence to your GRC platform (ServiceNow, Archer, Vanta, Drata)
- Build custom compliance dashboards from live event streams
2. Quick Start (5 Steps)
Create a Subscription
curl -X POST https://sovereign.tenova.io/api/v1/webhooks \
-H "Cookie: axiom_session=..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/swt3-webhook",
"event_types": ["verdict.failed", "drift.detected"],
"description": "Security alerts"
}'
Response includes a secret field (whsec_...). Copy it immediately -- it is shown once.
Send a Test Ping
curl -X POST https://sovereign.tenova.io/api/v1/webhooks/{id}/test \
-H "Cookie: axiom_session=..."
Your endpoint should receive a ping event. If it does not, check the delivery history (Step 5).
Deploy Your Receiver
Your webhook receiver must:
- Accept POST requests with
Content-Type: application/json - Return HTTP 200-299 within 10 seconds
- Be accessible via HTTPS (HTTP allowed only for localhost during development)
Validate the HMAC Signature
Every delivery includes an X-SWT3-Signature header. Verify it before processing. See Section 5 for code examples.
Go Live
Add the remaining event types you need. Monitor delivery health at GET /api/v1/webhooks/{id}/deliveries.
3. Event Types
| Event Type | Fires When | Typical Use |
|---|---|---|
verdict.issued | A PASS verdict is recorded in the ledger | Evidence stream, compliance dashboards |
verdict.failed | A FAIL verdict is recorded | Alerts, incident response triggers |
drift.detected | A control changes from PASS to FAIL between scans | CA-7 continuous monitoring, SIEM correlation |
attestation.lapsed | A manual attestation expires without renewal | Compliance officer alerts |
score.threshold | Sovereign Score drops below configured threshold | Executive dashboards, SLA monitoring |
hw.attestation.stale | Hardware attestation exceeds staleness window | Infrastructure monitoring |
hw.drift.detected | Hardware configuration changes detected on an agent | Supply chain integrity, SBOM drift |
ping | Test event sent via the test endpoint | Connectivity verification |
4. Payload Format
Every webhook delivery is a POST request with this JSON structure:
{
"event_id": "evt_a1b2c3d4e5f6",
"event_type": "verdict.failed",
"timestamp": "2026-08-13T12:00:00.000Z",
"tenant_id": "YOUR_TENANT",
"data": {
"procedure_id": "AI-GRD.1",
"verdict": "FAIL",
"swt3_anchor": "SWT3-E-VULTR-AI-AI-GRD.1-FAIL-1786624093-abc123def456",
"factor_a": 2,
"factor_b": 0,
"factor_c": 0,
"clearing_level": 1,
"ai_model_id": "gpt-4o",
"verification_url": "/api/v1/attest/verify?token=SWT3-..."
}
}
| Field | Type | Description |
|---|---|---|
event_id | string | Unique event identifier for idempotency |
event_type | string | One of the 8 event types above |
timestamp | string | ISO 8601 UTC timestamp |
tenant_id | string | Your tenant identifier |
data | object | Event-specific payload (varies by event type) |
event_id to deduplicate. In rare cases (network retry), the same event may be delivered twice. Your receiver should be idempotent -- processing the same event_id twice should have no side effects.
5. HMAC Signature Verification
Every delivery includes an X-SWT3-Signature header containing the HMAC-SHA256 hex digest of the raw request body, signed with your webhook secret.
Verification Steps
- Read the raw request body as bytes (do NOT parse JSON first).
- Compute HMAC-SHA256 using your webhook secret as the key and the raw body as the message.
- Compare the computed hex digest with the
X-SWT3-Signatureheader using constant-time comparison. - If they match, process the event. If not, return 401 and discard the request.
import hmac, hashlib, json
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = "whsec_your_secret_here"
@app.route("/swt3-webhook", methods=["POST"])
def handle_webhook():
signature = request.headers.get("X-SWT3-Signature", "")
body = request.get_data() # raw bytes, NOT request.json
expected = hmac.new(
WEBHOOK_SECRET.encode(), body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, signature):
abort(401, "Invalid signature")
event = json.loads(body)
print(f"Received {event['event_type']}: {event['data']['procedure_id']}")
return "OK", 200
import { createHmac, timingSafeEqual } from "crypto";
import express from "express";
const app = express();
const WEBHOOK_SECRET = "whsec_your_secret_here";
app.post("/swt3-webhook", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-swt3-signature"] as string;
const body = req.body; // raw Buffer from express.raw()
const expected = createHmac("sha256", WEBHOOK_SECRET)
.update(body)
.digest("hex");
if (!timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(body.toString());
console.log(`Received ${event.event_type}: ${event.data.procedure_id}`);
res.status(200).send("OK");
});
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io"
"net/http"
)
const webhookSecret = "whsec_your_secret_here"
func webhookHandler(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
signature := r.Header.Get("X-SWT3-Signature")
mac := hmac.New(sha256.New, []byte(webhookSecret))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(expected), []byte(signature)) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}
require "sinatra"
require "openssl"
require "json"
WEBHOOK_SECRET = "whsec_your_secret_here"
post "/swt3-webhook" do
body = request.body.read
signature = request.env["HTTP_X_SWT3_SIGNATURE"]
expected = OpenSSL::HMAC.hexdigest("SHA256", WEBHOOK_SECRET, body)
unless Rack::Utils.secure_compare(expected, signature)
halt 401, "Invalid signature"
end
event = JSON.parse(body)
puts "Received #{event['event_type']}: #{event['data']['procedure_id']}"
status 200
"OK"
end
6. Delivery and Retries
| Attempt | Delay | Total Wait |
|---|---|---|
| 1st (initial) | Immediate | 0s |
| 2nd (retry 1) | 10 seconds | 10s |
| 3rd (retry 2) | 60 seconds | 70s |
After 3 failed attempts, the delivery is marked as failed. The subscription remains active for future events.
Delivery States
| State | Meaning |
|---|---|
pending | Queued, not yet attempted |
success | Delivered, received HTTP 2xx |
retrying | Failed, retry scheduled |
failed | All retry attempts exhausted |
What Counts as Success
Any HTTP 200-299 response within 10 seconds. Your endpoint must respond within this window or the delivery times out and retries.
Response Body Capture
The first 1,024 bytes of your endpoint's response body are captured and stored in the delivery record. This helps debug issues when your endpoint returns an error message.
7. Debugging Checklist
If your webhook is not firing, work through this checklist in order:
- Tier check: Are you on Enclave or Sovereign?
GET /api/v1/healthdoes not require auth, but webhooks do require Enclave+. - Subscription active?
GET /api/v1/webhooks-- checkis_active: true. - Event types match? If you subscribed to
verdict.failedbut all inferences are PASS, no webhook fires. Addverdict.issuedfor PASS events. - URL reachable? Your endpoint must be publicly accessible via HTTPS. Test:
curl -I https://your-server.com/swt3-webhook. - Send a test ping:
POST /api/v1/webhooks/{id}/test. This sends apingevent immediately. - Check delivery history:
GET /api/v1/webhooks/{id}/deliveries. Look atstate,status_code, andresponse_body. - Check your server logs: Is the request arriving? Is HMAC verification failing? Is your endpoint returning 500?
- Firewall/WAF: Some WAFs block POST requests from unfamiliar user agents. Allowlist the SWT3 webhook user agent or IP range.
GET /api/v1/webhooks/{id}/deliveries?limit=5 shows the last 5 delivery attempts with HTTP status codes and response bodies. This tells you whether the problem is on your side (500) or on the network (timeout).
8. Managing Subscriptions
curl -X POST https://sovereign.tenova.io/api/v1/webhooks \
-H "Cookie: axiom_session=..." \
-H "Content-Type: application/json" \
-d '{"url":"https://hooks.slack.com/services/T.../B.../xxx","event_types":["verdict.failed"],"description":"Slack FAIL alerts"}'
List all subscriptions with last delivery status. Secrets are masked.
Update URL, event types, or active status. Cannot change the secret (delete and recreate instead).
Delete a subscription. Delivery history is retained for audit purposes.
Send a test ping event. Returns the delivery result including HTTP status and response body.
View delivery history. Includes state, HTTP status code, response body (first 1,024 bytes), and timestamp.
9. Integration Patterns
Slack: FAIL Alerts
Create a Slack Incoming Webhook and point the SWT3 webhook at it. Subscribe to verdict.failed and drift.detected. Slack will display the raw JSON payload -- for formatted messages, use a middleware (AWS Lambda, Cloudflare Worker) that transforms the payload into Slack Block Kit format.
Datadog: Compliance Events
Forward webhook payloads to the Datadog HTTP API (POST /api/v2/logs). Map event_type to Datadog tags. Use verdict.issued and verdict.failed for full evidence streams. Build dashboards on procedure_id facets.
PagerDuty: Incident Triggers
Use the PagerDuty Events API v2. Map verdict.failed to trigger and drift.detected to change. Include the swt3_anchor in the incident body for traceability.
ServiceNow: GRC Integration
Create a ServiceNow Scripted REST API endpoint. Map SWT3 events to GRC policy exceptions or control test results. The procedure_id maps to your ServiceNow control catalog. See the GRC Platform Integration Guide for detailed field mapping.
10. Limits and Constraints
| Constraint | Value |
|---|---|
| Max subscriptions per tenant | 5 |
| URL protocol | HTTPS required (HTTP allowed for localhost only) |
| Response timeout | 10 seconds |
| Retry attempts | 3 (initial + 2 retries) |
| Retry backoff | 0s, 10s, 60s |
| Response body captured | First 1,024 bytes |
| Minimum tier | Enclave ($9,500/mo) |
Secret Rotation
Webhook secrets cannot be rotated in place. To rotate: delete the subscription and create a new one. Your endpoint will receive events with the new secret. Plan for a brief gap during the switchover.
For the complete endpoint reference, see the API Reference -- Webhooks. For common issues, see the Troubleshooting FAQ -- Webhooks.