Page history
webhooks
managed-agents/webhooks
History
managed-agents/webhooks Changed · +2 / -0 lines
from line 12
<Tabs> <Tab title="Session events"> + Some of these events are named differently from the matching events on the session's [event stream](https://platform.claude.com/docs/en/managed-agents/events-and-streaming). For example, the stream's `session.status_idle` and `session.status_running` correspond to the `session.status_idled` and `session.status_run_started` webhook events. + | Event | Trigger | | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `session.status_run_started` | Agent execution started. This triggers at every session status transition to `running`. |
managed-agents/webhooks First recorded · 434 lines, first recorded
## Supported event types ## Register an endpoint ## Verify the signature ## Handle an event ## Delivery behavior
The first capture of this source. The page was already there, and this is what it said.
---
title: Subscribe to webhooks
url: https://platform.claude.com/docs/en/managed-agents/webhooks
description: Get notified when major events happen without polling.
---
Sessions are long-running interactions. While most real-time interactions happen through the [SSE event stream](https://platform.claude.com/docs/en/managed-agents/events-and-streaming), webhooks notify you of major state changes.
Webhook events return the event `type` and `id`, not the full object. When you receive a webhook event, you need to fetch the object directly with a `GET` call. This avoids delivering stale data on retries and keeps every delivery small.
## Supported event types
<Tabs>
<Tab title="Session events">
| Event | Trigger |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `session.status_run_started` | Agent execution started. This triggers at every session status transition to `running`. |
| `session.status_idled` | Agent awaiting input, for example, a tool permission approval or a new user message. |
| `session.budget_reached` | The session reached its [budget](https://platform.claude.com/docs/en/managed-agents/budgets) and paused. Fires at most once for each budget value you set; changing the budget arms it again. |
| `session.status_rescheduled` | A transient error occurred and the session is retrying automatically. |
| `session.status_terminated` | The session terminated, either because of an unrecoverable error or because it was archived. |
| `session.thread_created` | New [multiagent thread](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration) opened: an additional agent called by the coordinator is starting work, or the session's [advisor](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#give-the-session-an-advisor) is being consulted. |
| `session.thread_idled` | An agent in a [multiagent interaction](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration) is waiting for input. |
| `session.thread_terminated` | A [multiagent thread](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration) terminated, either because the thread was archived or because it exhausted its retries. A coordinator-spawned child that finishes its work goes `idle`, not `terminated` (an advisor thread terminates once its consultation completes). Fires for child threads only; the primary thread's end, including archiving the whole session, surfaces only as `session.status_terminated`. |
| `session.outcome_evaluation_ended` | [Outcome evaluation](https://platform.claude.com/docs/en/managed-agents/define-outcomes) for a single iteration completed. |
| `session.updated` | Session properties changed (for example, its name or configuration was updated). |
| `session.deleted` | Session permanently deleted. There is no object left to fetch, so treat the event itself as final. |
</Tab>
<Tab title="Vault events">
| Event | Trigger |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `vault.created` | Vault created. |
| `vault.archived` | Vault archived. A `vault_credential.archived` event is also emitted for each underlying credential. |
| `vault.deleted` | Vault deleted. A `vault_credential.deleted` event is also emitted for each underlying credential. There is no object left to fetch, so treat the event itself as final. |
| `vault_credential.created` | Credential created. |
| `vault_credential.archived` | Credential archived, either directly or as a result of vault archival. |
| `vault_credential.deleted` | Credential deleted, either directly or as a result of vault deletion. There is no object left to fetch, so treat the event itself as final. |
| `vault_credential.refresh_failed` | An `mcp_oauth` credential cannot be refreshed (invalid refresh token, or irrecoverable error from the OAuth server). |
</Tab>
<Tab title="Agent events">
These events track the lifecycle of the agent resources in your workspace, and are distinct from the agent events delivered on a session's event stream.
| Event | Trigger |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agent.created` | Agent created. |
| `agent.updated` | A [new version of the agent](https://platform.claude.com/docs/en/managed-agents/agent-setup#update-an-agent) was published. Updates that do not create a new version do not trigger this event. |
| `agent.archived` | Agent archived. |
| `agent.deleted` | Agent permanently deleted. There is no object left to fetch, so treat the event itself as final. |
</Tab>
<Tab title="Deployment events">
| Event | Trigger |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `deployment.created` | [Scheduled deployment](https://platform.claude.com/docs/en/managed-agents/scheduled-deployments) created. |
| `deployment.updated` | Deployment properties changed (for example, its schedule was updated). |
| `deployment.paused` | Deployment paused, either by request or automatically when a scheduled run fails with an unrecoverable error, such as an archived subagent or an archived environment. Recoverable failures, including rate limits, don't pause the deployment. See [Failure behavior](https://platform.claude.com/docs/en/managed-agents/scheduled-deployments#failure-behavior). |
| `deployment.unpaused` | Deployment unpaused, resuming its schedule. |
| `deployment.archived` | Deployment archived, either directly or because its agent was archived. If the agent is deleted instead, a scheduled deployment is archived at its next scheduled run; a deployment without a schedule is not archived automatically. |
| `deployment.deleted` | Deployment permanently deleted. There is no object left to fetch, so treat the event itself as final. |
</Tab>
<Tab title="Deployment run events">
| Event | Trigger |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `deployment_run.started` | A scheduled run started. Only scheduled runs emit `deployment_run` events; [manual runs](https://platform.claude.com/docs/en/managed-agents/scheduled-deployments#trigger-a-manual-run) do not. |
| `deployment_run.succeeded` | A scheduled run created its session. The event carries the same `data.id` (the run ID) as the run's `deployment_run.started` event. To follow the session's work, subscribe to its session events (the Session events tab), or fetch the [deployment run](https://platform.claude.com/docs/en/managed-agents/scheduled-deployments#deployment-runs) for its `session_id`. |
| `deployment_run.failed` | A scheduled run did not create a session. The event carries the same `data.id` as the run's `deployment_run.started` event. Fetch the [deployment run](https://platform.claude.com/docs/en/managed-agents/scheduled-deployments#deployment-runs) for the error details. |
</Tab>
<Tab title="Environment events">
| Event | Trigger |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `environment.created` | Environment created. |
| `environment.updated` | Environment updated with at least one changed field. A no-op update emits nothing. |
| `environment.archived` | Environment archived. Re-archiving an already-archived environment emits nothing. |
| `environment.deleted` | Environment deleted, including delete of an already-archived environment. There is no object left to fetch, so treat the event itself as final. |
An environment's [work items](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes) emit no webhook events.
</Tab>
<Tab title="Memory store events">
| Event | Trigger |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `memory_store.created` | Memory store created, either by you or by an Anthropic-operated process that clones one of your existing stores. |
| `memory_store.archived` | Memory store archived. Re-archiving an already-archived store emits nothing. |
| `memory_store.deleted` | Memory store deleted, including delete of an already-archived store. Deleting a store cascades to its memories and memory versions without emitting per-memory events; the single `memory_store.deleted` event is the signal. There is no object left to fetch, so treat the event itself as final. |
Individual [memories](https://platform.claude.com/docs/en/managed-agents/memory) and memory versions emit no webhook events.
</Tab>
</Tabs>
## Register an endpoint
Visit **Manage > Webhooks** in the [Claude Console](https://platform.claude.com/settings/workspaces/default/webhooks).
A webhook endpoint consists of:
* **URL:** Must be HTTPS on port 443 with a publicly resolvable hostname.
* **Event types:** The list of `data.type` values this endpoint receives. An endpoint only receives events it's subscribed to.
* **Signing secret:** A 32-byte `whsec_`-prefixed secret generated at creation. It's shown only once, so store it securely to verify webhook deliveries.
## Verify the signature
Every delivery carries the `webhook-id`, `webhook-timestamp`, and `webhook-signature` headers. Use the SDK's `unwrap()` helper to verify the signature and parse the event in one step. It throws if the signature is invalid or the payload is more than 5 minutes old.
Set `ANTHROPIC_WEBHOOK_SIGNING_KEY` to the `whsec_`-prefixed secret shown at endpoint creation.
<CodeGroup>
```python Python
from flask import Flask, request
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_WEBHOOK_SIGNING_KEY from env
app = Flask(__name__)
@app.route("/webhook", methods=["POST"])
def webhook():
try:
# unwrap() raises if the signature is invalid or the payload is stale
event = client.beta.webhooks.unwrap(
request.get_data(as_text=True),
headers=dict(request.headers),
)
except Exception:
return "invalid signature", 400
if event.data.type == "session.status_idled":
print("session idled:", event.data.id)
# handle other event types
return "", 200
```
```typescript TypeScript
import express from "express";
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic(); // reads ANTHROPIC_WEBHOOK_SIGNING_KEY from env
const app = express();
// IMPORTANT: use express.raw(), not express.json(). The signature is computed over raw bytes.
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
let event;
try {
// unwrap() throws if the signature is invalid or the payload is stale
event = client.beta.webhooks.unwrap(req.body.toString("utf8"), {
headers: req.headers as Record<string, string>
});
} catch {
return res.status(400).send("invalid signature");
}
switch (event.data.type) {
case "session.status_idled":
console.log("session idled:", event.data.id);
break;
// handle other event types
}
res.sendStatus(200);
});
```
```csharp C#
using Anthropic;
var client = new AnthropicClient(); // reads ANTHROPIC_WEBHOOK_SIGNING_KEY from env
var app = WebApplication.Create(args);
app.MapPost("/webhook", async (HttpRequest request) =>
{
using var reader = new StreamReader(request.Body);
var body = await reader.ReadToEndAsync();
var headers = request.Headers.ToDictionary(header => header.Key, header => header.Value.ToString());
UnwrapWebhookEvent webhookEvent;
try
{
// Unwrap() throws if the signature is invalid or the payload is stale
webhookEvent = client.Beta.Webhooks.Unwrap(body, headers);
}
catch
{
return Results.BadRequest("invalid signature");
}
if (webhookEvent.Data.TryPickSessionStatusIdled(out var idled))
{
Console.WriteLine($"session idled: {idled.ID}");
}
// handle other event types
return Results.Ok();
});
```
```go Go
package main
import (
"fmt"
"io"
"net/http"
"github.com/anthropics/anthropic-sdk-go"
)
var client = anthropic.NewClient() // reads ANTHROPIC_WEBHOOK_SIGNING_KEY from env
func webhook(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "could not read body", http.StatusBadRequest)
return
}
// Unwrap returns an error if the signature is invalid or the payload is stale
event, err := client.Beta.Webhooks.Unwrap(body, r.Header)
if err != nil {
http.Error(w, "invalid signature", http.StatusBadRequest)
return
}
switch event.Data.Type {
case "session.status_idled":
fmt.Println("session idled:", event.Data.ID)
// handle other event types
}
w.WriteHeader(http.StatusOK)
}
func main() {
http.HandleFunc("/webhook", webhook)
}
```
```java Java
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.UnwrapWebhookParams;
import com.anthropic.core.http.Headers;
import com.sun.net.httpserver.HttpServer;
// reads ANTHROPIC_WEBHOOK_SIGNING_KEY from env
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
void main() throws Exception {
var server = HttpServer.create(new InetSocketAddress(8000), 0);
server.createContext("/webhook", exchange -> {
var body = new String(exchange.getRequestBody().readAllBytes());
var headers = Headers.builder();
exchange.getRequestHeaders().forEach(headers::put);
try {
// unwrap() throws if the signature is invalid or the payload is stale
var event = client.beta().webhooks().unwrap(
UnwrapWebhookParams.builder()
.body(body)
.headers(headers.build())
.build());
event.data().sessionStatusIdled().ifPresent(idled ->
IO.println("session idled: " + idled.id()));
// handle other event types
exchange.sendResponseHeaders(200, -1);
} catch (Exception _) {
exchange.sendResponseHeaders(400, -1);
}
exchange.close();
});
}
```
```php PHP
use Anthropic\Client;
use Anthropic\Core\Exceptions\WebhookException;
$client = new Client(); // reads ANTHROPIC_WEBHOOK_SIGNING_KEY from env
$body = file_get_contents('php://input');
$headers = getallheaders();
try {
// unwrap() throws if the signature is invalid or the payload is stale
$event = $client->beta->webhooks->unwrap($body, headers: $headers);
} catch (WebhookException) {
http_response_code(400);
exit('invalid signature');
}
match ($event->data->type) {
'session.status_idled' => print "session idled: {$event->data->id}\n",
// handle other event types
default => null,
};
Cut at 300 lines.