Edictum
Edictum ConsoleNotifications

Webhook Setup

Send approval notifications to any HTTP endpoint. One-way notifications with optional HMAC-SHA256 signature verification.

AI Assistance

Right page if: you need to forward approval notifications to a custom HTTP endpoint (PagerDuty, OpsGenie, Teams, SIEM, or your own service). Wrong page if: you want interactive approve/deny buttons -- webhooks are one-way. Use https://docs.edictum.ai/docs/console/notifications/telegram, https://docs.edictum.ai/docs/console/notifications/slack, or https://docs.edictum.ai/docs/console/notifications/discord. Gotcha: HMAC-SHA256 signature verification must use the raw request body bytes, not parsed JSON. The webhook sends two event types: `approval_requested` and `approval_decided` -- design your receiver to ignore unknown event types.

Send approval notifications to any HTTP endpoint. One-way notifications -- no interactive approve/deny.

Add the Channel

Dashboard > Settings > Notifications > Add Channel > Webhook.

FieldRequiredDescription
URLYesHTTPS endpoint that receives POST requests
SecretNoHMAC-SHA256 secret for payload verification

Event Types

The console sends two event types via webhook:

EventTriggerDirection
approval_requestedAgent tool call requires human approvalConsole to your endpoint
approval_decidedA reviewer approves or denies a pending requestConsole to your endpoint

Future event types (tool-call audit events, deployment notifications, agent status changes) may be added. Design your receiver to ignore unknown event values gracefully.

Payload Format

approval_requested

Sent when an agent's tool call requires human approval:

POST https://your-endpoint.com/webhook
Content-Type: application/json
X-Edictum-Signature: sha256=abc123...

{
  "event": "approval_requested",
  "approval_id": "550e8400-e29b-41d4-a716-446655440000",
  "agent_id": "prod-agent",
  "tool_name": "run_command",
  "tool_args": {"command": "rm -rf /tmp/cache"},
  "message": "Destructive command requires approval",
  "env": "production",
  "timeout_seconds": 300,
  "timeout_effect": "deny",
  "tenant_id": "tenant-uuid",
  "contract_name": "destructive-ops"
}
FieldTypeDescription
eventstringAlways "approval_requested"
approval_idstringUnique ID for this approval request
agent_idstringThe agent that triggered the tool call
tool_namestringName of the tool being called
tool_argsobject or nullArguments passed to the tool
messagestringHuman-readable description of why approval is needed
envstringDeployment environment (e.g. "production")
timeout_secondsintHow long the agent waits before the timeout effect applies
timeout_effectstringWhat happens on timeout: "deny" or "allow"
tenant_idstringTenant that owns this agent
contract_namestring or nullThe contract that triggered the approval (omitted if not applicable)

approval_decided

Sent when a reviewer approves or denies a pending approval:

POST https://your-endpoint.com/webhook
Content-Type: application/json
X-Edictum-Signature: sha256=abc123...

{
  "event": "approval_decided",
  "approval_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "approved",
  "decided_by": "ops-team@example.com",
  "reason": "Cache clear is safe during maintenance window"
}
FieldTypeDescription
eventstringAlways "approval_decided"
approval_idstringMatches the original approval_requested event
statusstring"approved" or "denied"
decided_bystring or nullWho made the decision (null for timeout-based decisions)
reasonstring or nullOptional explanation from the reviewer

Signature Verification

If you configure a secret, every request includes an X-Edictum-Signature header. The signature is an HMAC-SHA256 hash of the request body using your secret.

Verify it on your end:

import hmac
import hashlib

def verify_signature(body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(), body, hashlib.sha256
    ).hexdigest()
    received = signature.removeprefix("sha256=")
    return hmac.compare_digest(expected, received)

Example Receiver

A minimal FastAPI webhook receiver:

from fastapi import FastAPI, Request, HTTPException

app = FastAPI()
SECRET = "your-webhook-secret"

@app.post("/webhook")
async def handle_webhook(request: Request):
    body = await request.body()
    signature = request.headers.get("X-Edictum-Signature", "")

    if not verify_signature(body, signature, SECRET):
        raise HTTPException(403, "Invalid signature")

    data = await request.json()
    event = data.get("event", "")

    if event == "approval_requested":
        print(f"Approval needed: {data['agent_id']} wants to call {data['tool_name']}")
        # Forward to PagerDuty, OpsGenie, your internal system, etc.
    elif event == "approval_decided":
        print(f"Approval {data['approval_id']}: {data['status']}")
    else:
        print(f"Unknown event type: {event}")

    return {"ok": True}

Use Cases

Webhooks are useful for integrating with systems that don't have a dedicated channel type:

  • Forward to PagerDuty or OpsGenie for on-call routing
  • Post to Microsoft Teams via incoming webhook
  • Trigger a CI/CD pipeline
  • Log to a SIEM
  • Send to a custom internal dashboard

Test

Click Test in the dashboard or call:

POST /api/v1/notifications/channels/{id}/test

A test payload is sent to your URL. Check that your endpoint receives it and responds with a 2xx status.

Troubleshooting

ProblemFix
Test fails with connection errorVerify the URL is reachable from the console server
Signature mismatchVerify you're using the raw request body (not parsed JSON) for HMAC
No requests receivedCheck firewall rules -- the console server must be able to reach your endpoint
Timeout on testYour endpoint must respond within 10 seconds

Next Steps

Last updated on

On this page