A webhook endpoint receives an event when something happens in your organization that it subscribed to: an approval is requested, a call is denied, a receipt is issued. There are 14 event types you can subscribe to, plus webhook.test, which is sent only when you ask for it.
K-Block records each event together with the change it reports, so an event exists only if the change really happened. Deliveries are created at that moment for the endpoints that are enabled and subscribed to the type: an endpoint that is disabled when an event is recorded never receives that event, even after it is enabled again. This page lists the events and their payloads. How deliveries are signed, retried and redelivered is described in Webhook delivery; creating endpoints in Webhooks.
Event envelope
Every delivery has the same JSON body:
{
"id": "<event-id>", // a UUID, the same for every delivery and redelivery of this event
"type": "approval.requested",
"created_at": "2026-01-15T09:30:00.000Z",
"organization_id": "<org-id>",
"data": { /* depends on the type, see below */ }
}Use id to ignore an event you already processed. Event data carries IDs you can look up in the Cloud API and short descriptions. It never contains tool arguments or the upstream's result; only error_detail of execution.failed may quote the upstream's error text.
Event types at a glance
The dashboard groups the types under Approvals, Decisions, Executions, Receipts, Policies and Observe mode when you choose an endpoint's events. It groups by the type's prefix, so action.observe_override is listed under Decisions.
| Type | Sent when |
|---|---|
approval.requested | A call needs approval and an approval request is created |
approval.approved | An approver approves a request |
approval.rejected | An approver rejects a request |
approval.expired | A pending request expires, or its action is superseded |
action.allowed | A call is allowed without approval |
action.denied | A call is denied |
action.authorized | A decision-only call is authorized for the caller to execute |
execution.completed | An approved or allowed call ran and succeeded |
execution.failed | A started execution failed, or an interrupted one was recovered as failed |
receipt.created | A receipt is issued |
policy.activated | A policy version comes into force: activation, shadow promotion, rollback or emergency stop |
agent.observe_started | An observe window starts for an agent |
agent.observe_ended | An observe window ends |
action.observe_override | An observe window allowed a call the policy would have denied or held for approval |
webhook.test | You send a test event to one endpoint |
Common fields
Several events describe one protected action with these fields:
| Field | Meaning |
|---|---|
protected_action_id | The action's ID |
agent_id | The agent that made the call |
tool | The tool's name |
action | The tool's action name, which policies decide on |
amount, currency | The call's amount as a decimal string and its currency, or null when the call has none |
environment | live or test: the environment of the gateway key the call used |
thread_id | The ID of the action's evidence thread (its TASK object), or null |
The decision events (approval.requested, action.allowed, action.denied, action.observe_override) also carry a policy object:
| Field | Meaning |
|---|---|
policy_id, version, digest | The policy, version number and document digest that decided, or null when no policy was evaluated |
rule_id | The rule that decided, or null when the default decided |
policy_effect | What the policy itself decided: allow, require_approval, deny, or null |
enforcement | enforce, or observe under an observe window |
relaxed | true when the decision applied allow although the policy said otherwise |
observe_window_id | The observe window, or null |
Approvals
approval.requested
Sent when the Gateway decides that a call requires approval. The action becomes pending_approval.
data: the common action fields, plus
| Field | Meaning |
|---|---|
approval_request_id | The approval request to decide |
expires_at | When the request expires if nobody decides |
approver_kids | The KIDs of the eligible approvers, fixed when the request was created |
policy | The decision's policy object |
approval.approved
Sent when an eligible approver signs an approval. The action becomes approved; it runs when the agent repeats the call, or at once if the agent is still waiting for the decision.
data:
| Field | Meaning |
|---|---|
approval_request_id, protected_action_id | The request and its action |
agent_id, tool, action | The call |
decided_by | The user ID of the approver |
kid | The KID the approver signed with |
decision_object_id | The ID of the signed APPROVAL object |
thread_id | The action's evidence thread |
approval.rejected
Sent when an eligible approver signs a rejection. The action becomes rejected and a receipt is issued (a receipt.created event follows). data has the same fields as approval.approved; decision_object_id is the signed REJECTION.
approval.expired
Sent when a pending approval request closes without a decision:
- it reached its expiry time (the action becomes
expiredwith codeAPPROVAL_EXPIRED), or - its action was superseded because it could no longer run as decided, for example after a policy rollback or emergency stop (code
SUPERSEDED).
data:
| Field | Meaning |
|---|---|
approval_request_id, protected_action_id | The request and its action |
agent_id, tool, action | The call |
expires_at | The request's expiry time |
reason | SUPERSEDED when the action was superseded; absent when the request expired |
Decisions
action.allowed
Sent when the Gateway allows a call without approval. The Gateway then executes it, and normally an execution.completed or execution.failed event follows. For a decision-only tool, action.authorized follows instead. If a check refuses the call before it runs (for example UPSTREAM_CHANGED), no execution event follows.
data: the common action fields and policy.
action.denied
Sent when the Gateway denies a call. Nothing runs and no receipt is issued.
data: the common action fields (without thread_id), policy, and
| Field | Meaning |
|---|---|
reason.code | Why it was denied, for example POLICY_DENIED or NO_DELEGATION (see Error codes) |
reason.message | A readable explanation |
action.authorized
Sent when the Gateway hands the authorization of an allowed or approved decision-only call to the caller, which is responsible for executing it. The action becomes authorized and an authorization receipt is issued (receipt.created follows, with kind authorization). K-Block executes nothing, sends no execution event for it, and cannot confirm whether the caller executed the call.
data: the common action fields, and
| Field | Meaning |
|---|---|
approval_request_id | The approval request when a person approved the call, else null |
receipt_id | The receipt of the authorization |
execution | Always caller |
Executions
execution.completed
Sent when the upstream call of an allowed or approved action succeeded and the result was recorded. The action becomes succeeded.
data:
| Field | Meaning |
|---|---|
protected_action_id, execution_id | The action and its execution |
agent_id, tool, action | The call |
status | succeeded |
result_object_id | The ID of the signed RESULT object |
receipt_id | The receipt's Cloud API ID |
execution.failed
Sent when an execution ends as failed, in two situations:
- The Gateway recorded the failure: the upstream reported an error, could not be reached, or its result could not be accepted.
datahas the fields ofexecution.completed, withstatusfailedanderror_detail, a description of the failure (shortened when long). When the error text contains a value in a known secret format (a K-Block API key, a webhook signing secret, a payment provider's API key, a private key),error_detailis left out anderror_detail_omittedistrue. Any other text is sent as the upstream wrote it. (The audit log never stores this text: itsexecution.failedevent haserror_detail_omitted: trueinstead.) - An interrupted execution was recovered: the execution started but its outcome was never recorded, and recovery marked it failed later (code
INTERRUPTED).datahasprotected_action_id,execution_id,agent_id,tool,action,statusfailed,error_detail,receipt_id(nullwhen no receipt could be signed),result_object_idwhen a RESULT was signed, andupstream_called: falsewhen the upstream is known not to have been called.
Receipts
receipt.created
Sent when a receipt is issued: after a successful or failed execution, a rejection, a cancellation, a recovered execution that could be signed, or a decision-only authorization.
data:
| Field | Meaning |
|---|---|
receipt_id | The receipt's Cloud API ID |
protected_action_id | The action |
receipt_object_id | The ID of the signed receipt object |
thread_id | The action's evidence thread |
outcome | succeeded, failed, final for rejections and cancellations, or pending / granted for a decision-only authorization |
kind | execution, authorization (decision only: the receipt proves the authorization, not an execution) or closure (rejected or cancelled) |
bundle_sha256 | The SHA-256 of the evidence bundle. Keep it with the bundle if you download it: K-Block Cloud deletes its hosted copy after the retention period |
See Receipt fields.
Policies
policy.activated
Sent when a policy version comes into force.
data:
| Field | Meaning |
|---|---|
policy_id, policy_name | The policy |
version_id, version, digest | The version now in force |
trigger | activate, promote_shadow, rollback or emergency |
previous_version_id, previous_version | The version it replaced, or null |
restored_from_version | For a rollback, the version number restored; otherwise null |
emergency_mode | For an emergency stop, deny_all or require_approval_all; otherwise null |
See Activate, roll out and roll back and Emergency stop.
Observe mode
agent.observe_started
Sent when an owner or admin starts an observe window for an agent.
data:
| Field | Meaning |
|---|---|
window_id | The observe window |
agent_id, agent_name | The agent |
environments | The environments it covers: live, test or both |
relax | default or default_and_approvals: which decisions the window relaxes |
ends_at | When the window ends at the latest |
reason | The reason given when it was started |
started_by | The user ID of the person who started it |
agent.observe_ended
Sent when an observe window ends.
data:
| Field | Meaning |
|---|---|
window_id, agent_id | The window and its agent |
end_reason | ended, expired, end_all, disabled_by_owner, live_disallowed, max_reduced, emergency_stop, agent_disabled or policy_changed (the agent was moved to another policy) |
ended_at | When it ended |
relaxed_count | How many calls the window relaxed |
action.observe_override
Sent once for each call an observe window relaxed: the policy would have denied it or held it for approval, but the window allowed it. It accompanies the call's action.allowed event.
data: the common action fields, policy, and observe with window_id, ends_at and relax.
See Observe and shadow.
webhook.test
Sent only to one endpoint, when someone selects Send test event on its page (or calls POST /api/v1/orgs/<org-id>/webhooks/<webhook-id>/test). The endpoint must be enabled. Endpoints cannot subscribe to this type, and other endpoints never receive it.
data:
| Field | Meaning |
|---|---|
endpoint_id | The endpoint the test was sent to |
message | "Test event sent from the K-Block Cloud dashboard" |
Changes that send no event
Some changes are recorded only in the audit log, with no webhook event in this version:
- Cancellations. Cancelling an action sends no
approval.*oraction.*event; the only event isreceipt.createdwith outcomefinal. - Approved calls that expire or are superseded. When an approved action expires (
TASK_EXPIRED) or is superseded before it runs, no event is sent. The approval request staysapproved. - Execution start. There is no event when an execution starts; only its end is reported.
- Administration. Member, invitation, API key, webhook endpoint, tool, MCP server, agent, delegation and organization settings changes, and policy drafts, shadow versions and examples, are audit events only. The exception is an observe window such a change ends (for example disabling an agent), which sends
agent.observe_ended.
If you mirror action states from webhooks, also poll GET /api/v1/orgs/<org-id>/actions for actions that stay open, or listen to receipt.created to see cancellations.