Webhook Integration

Webhooks let Tempered notify your systems when evaluations complete, removing the need to poll for results.

How Webhooks Work

  1. You register a webhook URL with Tempered
  2. When an evaluation completes (or fails), Tempered sends an HTTP POST to your URL
  3. Your endpoint processes the result and returns a 2xx status code
  4. If delivery fails, Tempered retries with exponential backoff

Setting Up a Webhook

Via the Dashboard

  1. Go to Settings → Webhooks
  2. Click Create Webhook
  3. Enter your endpoint URL (must be HTTPS)
  4. Copy the generated signing secret — you'll need this to verify payloads
  5. Select which events to receive (evaluation.completed, evaluation.failed)

Via the API

curl -X POST https://your-tempered-instance/api/v1/webhooks/ \
  -H "Authorization: Bearer prx_your_admin_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.example.com/webhooks/praxis",
    "events": ["evaluation.completed", "evaluation.failed"]
  }'

Response:

{
  "id": "wh-uuid-here",
  "url": "https://your-app.example.com/webhooks/praxis",
  "secret": "whsec_your_signing_secret",
  "events": ["evaluation.completed", "evaluation.failed"],
  "is_active": true
}

Event Types

Event Trigger
evaluation.completed An evaluation finished successfully with a verdict
evaluation.failed An evaluation failed (timeout, all vendors failed, etc.)

Payload Format

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "completed",
  "description": "Deploy updated auth service...",
  "context": {"environment": "production"},
  "change_request_id": "CHG-1234",
  "plan_id": "PLN-456",
  "recommendation": "PROCEED_WITH_MITIGATIONS",
  "approved": true,
  "opinions": [
    {
      "vendor_name": "anthropic",
      "model_id": "claude-sonnet-4-6",
      "recommendation": "PROCEED_WITH_MITIGATIONS",
      "confidence": 0.85,
      "dimensions": {
        "security": {"level": "low", "rationale": "..."},
        "reliability": {"level": "medium", "rationale": "..."}
      },
      "conditions": ["Verify staging test results before deployment"]
    }
  ]
}

Verifying Webhook Signatures

Every webhook request includes an HMAC-SHA256 signature for verification. Always verify signatures to ensure the payload came from Tempered.

Headers

Header Content
X-Praxis-Signature HMAC-SHA256 signature
X-Praxis-Timestamp Unix timestamp of the request
X-Praxis-Event Event type (e.g., evaluation.completed)

Verification Algorithm

The signature is computed as:

HMAC-SHA256(secret, "{timestamp}.{payload_json}")

Python Example

import hashlib
import hmac
import time

def verify_webhook(payload: bytes, signature: str, timestamp: str, secret: str) -> bool:
    # Reject stale timestamps (>5 minutes old)
    if abs(time.time() - int(timestamp)) > 300:
        return False

    expected = hmac.new(
        secret.encode(),
        f"{timestamp}.{payload.decode()}".encode(),
        hashlib.sha256,
    ).hexdigest()

    return hmac.compare_digest(expected, signature)

Flask Example

from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = "whsec_your_signing_secret"

@app.route("/webhooks/praxis", methods=["POST"])
def handle_webhook():
    signature = request.headers.get("X-Praxis-Signature", "")
    timestamp = request.headers.get("X-Praxis-Timestamp", "")

    if not verify_webhook(request.data, signature, timestamp, WEBHOOK_SECRET):
        return jsonify({"error": "Invalid signature"}), 401

    data = request.json
    event = request.headers.get("X-Praxis-Event")

    if event == "evaluation.completed":
        handle_evaluation_result(data)

    return jsonify({"status": "ok"}), 200

Retry Policy

If your endpoint returns a non-2xx status code or times out, Tempered retries:

Attempt Delay
1st retry 30 seconds
2nd retry 2 minutes
3rd retry 10 minutes

After 3 failed attempts, the delivery is moved to a dead letter queue (DLQ). Failed deliveries can be reviewed and retried from the webhook management page.

Best Practices