Skip to main content
Webhooks deliver each classified command, tool call and secrets detection to your own HTTPS endpoint. Use them to stream activity into your SIEM, notify on-call, or trigger automations. Deliveries are notifications, not a gate: an event is sent after the activity has already run, and your response is recorded as a delivery result and nothing else — it cannot allow or stop the command. For an interactive approval gate, use the Require Slack Approval policy action instead — see Slack.

Prerequisites

  • Unbound account with Admin role
  • A public HTTPS endpoint you control to receive the events
  • (Optional) A server-side secret manager to store the signing secret

Setup

  1. In your Unbound dashboard, go to Settings → Webhooks
  2. Click Add Endpoint
  3. Enter your endpoint URL (must be https://, public, and not pointing at a private network)
  4. Optionally add a description
  5. Under Subscribe to events, select one or more events (Select at least one event until you do) — use Search events to filter, or Select all
  6. Optionally open Show advanced configuration to add custom headers
  7. Click Create
On creation, your endpoint’s whsec_… signing secret is shown once. Copy it now and store it in your secret manager. You can reveal it again from the endpoint detail page if you need to, but treat it like an API key.

Signature verification

Every delivery includes three headers your receiver can use to verify the event came from Unbound and wasn’t tampered with in transit. The verification algorithm is standard HMAC-SHA256:
  1. Strip the whsec_ prefix from your signing secret and base64-decode the remainder. The result is the HMAC key.
  2. Build the signed payload by concatenating <webhook-id>.<webhook-timestamp>.<raw-body>.
  3. Compute HMAC-SHA256(key, signed_payload) and base64-encode the digest. Prefix it with v1,.
  4. Compare against the webhook-signature header using a constant-time comparison. Unbound sends exactly one signature, so the header is a single v1,<base64> value.
  5. Reject deliveries whose webhook-timestamp is older than 5 minutes to prevent replay.
Always verify against the raw request body bytes, not a re-serialised JSON object. JSON re-serialisation can reorder keys or change whitespace, breaking the signature.

Event types

Each command or tool call can fire multiple events. The four per-action events fire whenever a policy of that action matches, and *.logged fires for everything. A command that matched a Block policy therefore produces two deliveries: *.blocked and *.logged. In the dashboard you tick individual events, a whole group, or Select all; the endpoint is subscribed to the events that exist at that moment, so edit it when new event types are added. Wildcard patterns (terminal_command.*, mcp_tool.*, secrets.*, ai_tool.*, or *) are accepted by the endpoint API and pick up new event types in that group automatically. *.logged is the firehose — every classified command or tool call. Use it for SIEM streaming; pick the per-action events if you only want policy-driven activity. The ai_tool.* events are your Shadow AI feed: they fire when a user is seen using a tool that isn’t on your sanctioned list. Route them somewhere a human will see them.

Payload

Every event uses the same envelope: a top-level id (prefixed msg_, not time-ordered), type (the event name), timestamp (ISO-8601, UTC), and a nested data block. The data block is consistent across all event types within a family — terminal-command events carry a command string, MCP-tool events carry mcp_server, mcp_tool, and mcp_parameters instead, and everything else is shared. A single command or tool call can trigger multiple events. Each event is a separate delivery with its own id and signature, but they share the same data.tool_use_id so you can dedupe across events if you want one record per command. Secrets events (secrets.blocked / secrets.audited) don’t carry tool_use_id — use request_id to dedupe those. Secrets events carry only the fields marked (secrets only) plus tool, user_email and thread_id; they do not include tool_name, prompt, intent_attribution, matched_policies or classifications. Shadow AI events (ai_tool.*) describe a tool on a device, not a single command, so they carry their own set of fields — see Shadow AI events below. They don’t carry tool_use_id, and there’s no per-command grouping to do: each delivery is a distinct alert. Deduplicate retries by webhook-id like any other event.

Field reference

Shadow AI events

ai_tool.unsanctioned_detected and ai_tool.unsanctioned_used report a user using an unsanctioned tool, so their data block is different from the command events above.
Deduplicating. To drop duplicate deliveries of the same event, use webhook-id — the idempotency key on every delivery, exactly as you would for any other event.Do not build a longer-lived idempotency key out of user_id + tool_name. These events are deliberately recurring: unsanctioned_detected fires once, the first time a tool is seen for a user, and unsanctioned_used fires at most once every 24 hours for the same user and tool for as long as they keep using it. Two unsanctioned_used deliveries share the same user_id and tool_name but are different alerts — collapsing on those fields would suppress the recurring-usage signal, which is the point of the event. They carry no tool_use_id and are not a per-command feed; treat each delivery as its own alert and key only retries on webhook-id.

Retries

Failed deliveries (anything that isn’t HTTP 200–299) retry on the following schedule: The retry horizon is approximately 28 hours total. Each attempt times out after 15 seconds. After 8 failed attempts the event is dropped.
Redirects (3xx) are treated as failures and not followed. Configure your endpoint to be the resolved URL.

Custom headers

You can attach static custom headers to every delivery for your endpoint — useful when your receiver requires a specific authentication header. Add headers under Show advanced configuration when you create the endpoint, or later from the endpoint page (Custom headers → Edit). Reserved headers (content-type, user-agent, host, and our signature headers) cannot be overridden.

Testing

To send a test event:
  1. Open your endpoint from Settings → Webhooks
  2. Click Send test event
  3. Pick an event type from the dropdown
  4. Click Send
A test event is signed and carries the same headers as a real event. Failed test deliveries are not retried so you can iterate quickly on your receiver. Test deliveries appear in the Message Attempts table tagged with test. Two things must be true for it to send. The endpoint has to be Enabled — a disabled one answers Endpoint is disabled. Re-enable it to send a test event. — and it has to be subscribed to the event type you picked, or you get Endpoint is not subscribed to that event type. Testing a specific event type therefore proves the subscription as well as the connection: pick the event you actually expect and you have tested the path it will take.

Managing endpoints

From the endpoint detail page you can:
  • Edit subscribed events — change which events trigger this endpoint
  • Edit description — update the human-readable label
  • Edit custom headers — change static headers sent on every delivery
  • Disable — stop firing without losing history (use the three-dot menu)
  • Delete — remove the endpoint and its delivery history permanently
The Success rate (24h) column on the endpoints list shows the percentage of HTTP 2xx responses in the last 24 hours. A dash means no deliveries in that window.

Troubleshooting

Verify the endpoint is Enabled, the events you expect are in Subscribed events, and the URL is reachable from the public internet. Use Send test event to confirm end-to-end connectivity. If you want to confirm any traffic is flowing, subscribe to terminal_command.logged and mcp_tool.logged — the firehose events fire for every classified call regardless of risk or policy match. Then double-check the Terminal Runs table on the Tool Use page for the activity you expected.
The most common cause is verifying against a parsed JSON object instead of the raw body bytes. Make sure your handler reads the raw request body before parsing. Also confirm you copied the full whsec_… secret without truncation.
Process the event asynchronously. Acknowledge with 200 immediately and queue the actual work — anything taking longer than 15 seconds will be treated as a failure and retried.
Delivery history is visible per-endpoint, but events are not currently replayable through the UI. Re-trigger the source action in your AI tool, or contact support to manually replay a specific delivery.

Tool Policies

Configure which actions trigger which event types

Slack

Pair webhooks with Slack approval workflows