Webhook Setup
Send approval notifications to any HTTP endpoint. One-way notifications with optional HMAC-SHA256 signature verification.
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.
| Field | Required | Description |
|---|---|---|
| URL | Yes | HTTPS endpoint that receives POST requests |
| Secret | No | HMAC-SHA256 secret for payload verification |
Event Types
The console sends two event types via webhook:
| Event | Trigger | Direction |
|---|---|---|
approval_requested | Agent tool call requires human approval | Console to your endpoint |
approval_decided | A reviewer approves or denies a pending request | Console 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"
}| Field | Type | Description |
|---|---|---|
event | string | Always "approval_requested" |
approval_id | string | Unique ID for this approval request |
agent_id | string | The agent that triggered the tool call |
tool_name | string | Name of the tool being called |
tool_args | object or null | Arguments passed to the tool |
message | string | Human-readable description of why approval is needed |
env | string | Deployment environment (e.g. "production") |
timeout_seconds | int | How long the agent waits before the timeout effect applies |
timeout_effect | string | What happens on timeout: "deny" or "allow" |
tenant_id | string | Tenant that owns this agent |
contract_name | string or null | The 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"
}| Field | Type | Description |
|---|---|---|
event | string | Always "approval_decided" |
approval_id | string | Matches the original approval_requested event |
status | string | "approved" or "denied" |
decided_by | string or null | Who made the decision (null for timeout-based decisions) |
reason | string or null | Optional 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}/testA test payload is sent to your URL. Check that your endpoint receives it and responds with a 2xx status.
Troubleshooting
| Problem | Fix |
|---|---|
| Test fails with connection error | Verify the URL is reachable from the console server |
| Signature mismatch | Verify you're using the raw request body (not parsed JSON) for HMAC |
| No requests received | Check firewall rules -- the console server must be able to reach your endpoint |
| Timeout on test | Your endpoint must respond within 10 seconds |
Next Steps
- Notification Overview -- routing filters and channel management
- Email Setup -- add email notifications
Last updated on
Email (SMTP) Setup
Send approval notifications via email with a deep link button to the dashboard for approve/deny.
How the Console Works
The coordination layer for governed AI agents. Agents evaluate contracts locally; the console stores events, manages approvals, pushes updates, and monitors the fleet.