Configure org webhooks
Wire your organization's webhook receiver so downstream systems (Slack, custom backend, monitoring) get notified when long-running operations complete or fail. Configured at
Organizations → <org> → Webhookstab. Org admin only.
What gets delivered
Webhooks fire on every async operation the platform completes or fails for this org:
helm.install— marketplace package installed on an edgehelm.upgrade— release upgradedhelm.uninstall— release removedvm.start/vm.stop/vm.restart/vm.migratevelero.backup/velero.restoreedge.discover— kubeconfig-driven edge import- … and any future async operation
Each delivery is a JSON POST to your URL with:
{
"event": "operation.completed", // or "operation.failed"
"operation_id": 12345,
"type": "helm.install",
"status": "completed",
"org_id": "uuid-of-org",
"user_id": "uuid-of-the-user-who-triggered",
"username": "alice@example.com",
"error": "", // populated when failed
"payload": { ... }, // original request body
"result": { ... }, // execution result (completed only)
"timestamp": "2026-05-13T12:34:56Z"
}
If a Bearer token is configured on the webhook, every POST
carries Authorization: Bearer <token>. Receivers should verify
it before accepting the payload.
Steps
Add a webhook
Organizations → <org> → Webhookstab.- + Add webhook.
- Fill:
- Name — human label, shows up in the list (e.g. "Slack ops channel", "Custom CRM").
- URL —
https://your-receiver/dtedge-events. Must be public-internet-reachable from the dtedge install (SSRF guard refuses private IPs, cluster-internal hostnames, metadata endpoints). - Bearer token (optional) — added as
Authorization: Bearer <token>on every delivery. Stored AES-encrypted at rest. Recommended. - Active — toggle off to pause deliveries without losing the row.
- Save.
Test a webhook
After save, the row's actions menu has Test. This dispatches a sample payload:
{
"event": "test",
"operation_id": 0,
"type": "test",
"status": "test",
"org_id": "<your-org-id>",
"timestamp": "<now>"
}
Use it to confirm:
- Your URL is reachable from dtedge
- The Bearer token (if any) is being sent correctly
- Your receiver returns 2xx (anything else counts as failure)
Response status appears in a toast.
View delivery history
Each webhook row has a History drawer. Each entry:
| Field | Notes |
|---|---|
| Timestamp | When the attempt happened |
| Event | operation.completed / operation.failed / test |
| Status code | HTTP response from your URL |
| Status | success / failed |
| Attempts | How many tries before this row landed in its final state (1 = first try succeeded) |
Retry a failed delivery
For rows where status: failed and retries exhausted, the actions
menu has Retry. Re-queues the payload as a fresh delivery
task. Useful when:
- Your receiver was down during the original window
- You fixed a bug in the receiver and want to replay missed events
Retry semantics
A single webhook delivery has 5 retry attempts with exponential backoff:
Attempt 1: immediate
Attempt 2: ~1 min later
Attempt 3: ~2 min
Attempt 4: ~4 min
Attempt 5: ~16 min
If all five fail (2xx never returned), the delivery is marked
failed and stays in the history. The receiver is treated as
"down for this event" — subsequent events for the same webhook
still attempt independently, so missing one doesn't break the
stream.
A receiver returning 2xx counts as success even if it discards the payload. We don't try to parse the response body.
URL restrictions
The save handler refuses:
- Cluster-internal hostnames (
*.svc.cluster.local,.internal) - Private IP literals (10.0.0.0/8, 192.168.0.0/16, etc.)
- Loopback (127.0.0.0/8)
- Metadata IPs (169.254.169.254, etc.)
- Non-HTTPS URLs in production (HTTP allowed only in
devmode)
DNS rebinding is defeated by re-checking the resolved IP at dial time, so a public hostname that resolves to a private IP is caught at delivery, not just at save.
These restrictions defeat SSRF — a malicious org admin (or a compromised one) can't use webhooks to probe internal services.
Common errors
| Symptom | Cause |
|---|---|
Save returns WEBHOOK_URL_BLOCKED | URL points at a private IP, cluster-internal hostname, or metadata endpoint |
Save returns WEBHOOK_URL_INVALID | Not a valid http(s) URL |
| Test fails with 401 | Your receiver requires a different auth mechanism than Bearer |
| Test fails with timeout | Receiver took > 10 seconds; we don't retry inside a single attempt |
| Every delivery fails | Receiver is down, or rejects our payload shape. Check the History drawer's Status code column. |
See also
- Reference → Error messages
- The dtedge backend's webhook subsystem (concepts page) covers the retry queue, dead letter, audit log.