* Update claude-api skill: Managed Agents self-hosted memory stores and web tool domain settings, cost-optimize subcommand, Admin API reference, ASCII-only text Managed Agents: self-hosted sandboxes can now attach memory stores via the SDK worker (handle_item, ANTHROPIC_WORK_SECRET, sync options, troubleshooting); web_search/web_fetch accept allowed_domains/blocked_domains/user_location/max_content_tokens on the toolset configs entry and are not governed by environment networking; typed per-tool config unions; Console session viewer notes; packages caveat under limited networking; Claude Platform on AWS self-hosted worker auth. New shared/cost-optimization.md backing a cost-optimize subcommand, and new shared/admin-api.md covering client.beta.organization in all SDKs and the CLI. Prompt caching gains TTL selection, automatic vs explicit breakpoint guidance, workspace isolation, and verification guidance. Sonnet 5 pricing is the permanent $2/$10 list price. Advisor pairing no longer excludes Claude Fable 5 for Managed Agents. Reviewer-only HTML comments are stripped from the published files. All files are now plain ASCII in prose (em dashes, arrows, emoji callouts, and box-drawing replaced with ASCII equivalents), matching the source so future syncs diff cleanly. No-Verification-Needed: documentation-only change to skill reference content * Update claude-api skill: Claude Fable 5.1 / Mythos 5.1 catalog rows, Files and Skills APIs out of beta Claude Fable 5.1 (claude-fable-5-1) and Claude Mythos 5.1 (claude-mythos-5-1) become the default Fable-tier models throughout the skill; Claude Fable 5 and Mythos 5 stay selectable by id with their own catalog rows. Feature-support lists that named Fable 5 now read Fable 5/5.1. The Files API and Skills API are out of beta: examples use client.files.* / client.skills.* with no beta header, and the API-drift table points at the beta-to-GA migration docs. No-Verification-Needed: documentation-only change to skill reference content * Update claude-api skill: Claude Fable 5.1 / Mythos 5.1 migration section and API changes Adds a "Migrating to Claude Fable 5.1 from Claude Fable 5" section to shared/model-migration.md: three breaking changes (forced tool_choice any/tool returns 400; thinking blocks are preserved only for the model that produced them or a newer one; and only in the conversation that produced them, so edited history replayed with thinking blocks is rejected), what carries over from Fable 5, the Opus 5 path, Mythos 5.1 notes, capability improvements, prompt-tunable behavioral shifts, and a migration checklist. New API features documented: per-message effort (mid-conversation-output-config beta), turn-scoped mid-conversation system messages with clear_at, progress updates between tool calls via thinking.display "updates", thinking block_binding controls, and the 0.025x cache-read rate on Fable 5.1 with a max_tokens: 0 keep-alive that usually beats the 1-hour TTL. Error catalog, prompt-caching, tool-use, platform-availability, cost-optimization, and prompt-audit are updated to match; SKILL.md routes migration and prompting questions to the new section. No-Verification-Needed: documentation-only change to skill reference content * Update claude-api skill: Claude Fable 5.1 launch-day hedges and migration-path table rows Adds claude-fable-5 -> claude-fable-5-1 and claude-mythos-5 -> claude-mythos-5-1 rows to the migration-path and model-ID mapping tables (including the Bedrock IDs), and updates the refusal-fallback example to the 5.1 model id. Hedges three claims until the launch docs confirm them: Task Budgets support on Claude Fable 5.1, whether Claude Mythos 5.1 shares the 0.025x cache-read rate, and the fallback-credit wording. The block_binding error row now says to send the controls beta header only where that beta is offered and to fall back to strip-and-retry elsewhere. Cross-references within the migration section point at the history-editing check directly. No-Verification-Needed: documentation-only change to skill reference content
11 KiB
Managed Agents - Webhooks
Anthropic can POST to your HTTPS endpoint when a Managed Agents resource changes state - an alternative to holding an SSE stream or polling. Payloads are thin (event type + resource IDs only); on receipt, fetch the resource for current state. Every delivery is HMAC-signed.
Direction matters. This page covers Anthropic -> you notifications about session/vault state. It does not cover third-party -> you webhooks that trigger a session (e.g. a GitHub push handler that calls
sessions.create()) - that's ordinary application code on your side with no Anthropic-specific wire format.
Register an endpoint (Console only)
Console -> Manage -> Webhooks. There is no programmatic endpoint-management API yet. Secret rotation is supported from the same page.
| Field | Constraint |
|---|---|
| URL | HTTPS on port 443, publicly resolvable hostname |
| Event types | Subscribe per data.type - an endpoint receives only the types it is subscribed to |
| Signing secret | whsec_-prefixed, 32 bytes, shown once at creation - store it |
Verify the signature
Every delivery carries the webhook-id, webhook-timestamp, and webhook-signature headers. Use the SDK's client.beta.webhooks.unwrap() - it verifies the signature, rejects payloads more than ~5 minutes old, and returns the parsed event. It reads the whsec_ secret from ANTHROPIC_WEBHOOK_SIGNING_KEY. Pass the headers through untouched; don't hand-roll verification against a single X-Webhook-Signature header, which is not the wire format.
import anthropic
from flask import Flask, request
client = anthropic.Anthropic() # reads ANTHROPIC_WEBHOOK_SIGNING_KEY from env
app = Flask(__name__)
@app.route("/webhook", methods=["POST"])
def webhook():
try:
event = client.beta.webhooks.unwrap(
request.get_data(as_text=True),
headers=dict(request.headers),
)
except Exception:
return "invalid signature", 400
if event.id in seen_event_ids: # dedupe retries - id is per-event, not per-delivery
return "", 204
seen_event_ids.add(event.id)
match event.data.type:
case "session.status_idled":
session = client.beta.sessions.retrieve(event.data.id)
notify_user(session)
case "vault_credential.refresh_failed":
alert_oncall(event.data.id)
return "", 204
Pass the raw request body to unwrap() - frameworks that re-serialize JSON (Express .json(), Flask .get_json()) change the bytes and break the MAC. For other languages, look up the beta.webhooks.unwrap binding in the SDK repo (shared/live-sources.md); don't hand-roll verification.
Payload envelope
{
"type": "event",
"id": "whe_9d5c1f7e...",
"created_at": "2026-03-18T14:05:22Z",
"data": {
"type": "session.status_idled",
"id": "session_01XYZ...",
"organization_id": "8a3d2f1e-...",
"workspace_id": "c7b0e4d9-..."
}
}
Switch on data.type, fetch the resource by data.id, return any 2xx to acknowledge. created_at is when the event occurred, not when the delivery was attempted - the webhook-timestamp header is the clock for the attempt (see Delivery behavior).
The top-level id is the same value as the webhook-id header, and it is per event, not per delivery - every retry carries it unchanged. Dedupe on it.
Supported data.type values
data.type |
Fires when |
|---|---|
session.status_scheduled |
Session created and ready to accept events |
session.status_run_started |
Agent execution kicked off (every transition to running) |
session.status_idled |
Agent awaiting input (tool approval, custom tool result, or next message) - or paused at its session budget. The webhook payload is thin - list the session's events and check the latest session.status_idle event's stop_reason (the session object itself has no stop_reason field): if it is budget_reached, further user.message events return a 400 and only a budget change/removal resumes the session (shared/managed-agents-core.md § Session budgets) |
session.status_rescheduled |
A transient error occurred; the session is retrying automatically |
session.status_terminated |
Session ended - on completion or on error, not error-only |
session.thread_created |
Multiagent: coordinator opened a new subagent thread, or the session's advisor is being consulted (shared/managed-agents-multiagent.md -> Advisor) |
session.thread_idled |
Child threads only: a subagent thread is waiting for input - or paused because the session reached its budget cap. When the whole session pauses at the cap, a session.status_idled webhook also fires and the stream's session.status_idle event carries stop_reason: budget_reached - unless another thread is waiting on a tool ask, which outranks the cap at the session level (shared/managed-agents-core.md § Session budgets). |
session.thread_terminated |
A thread ended - child completed its work, or the thread was archived. Child threads only; the primary thread's end surfaces as session.status_terminated |
session.outcome_evaluation_ended |
Outcome grader finished one iteration |
session.updated |
Session properties changed (name, configuration) |
session.deleted |
Session permanently deleted - no object left to fetch; treat the event itself as final |
vault.archived |
Vault was archived |
vault.created |
Vault was created |
vault.deleted |
Vault was deleted - a vault_credential.deleted also fires per underlying credential. No object left to fetch; treat the event itself as final |
vault_credential.archived |
Credential archived, directly or via vault archival |
vault_credential.created |
Vault credential was created |
vault_credential.deleted |
Credential deleted, directly or via vault deletion. No object left to fetch; treat the event itself as final |
vault_credential.refresh_failed |
MCP OAuth vault credential failed to refresh |
agent.created |
Agent created |
agent.updated |
A new agent version was published. Updates that do not create a new version do not fire this. |
agent.archived |
Agent archived |
agent.deleted |
Agent permanently deleted - no object left to fetch; treat the event itself as final |
deployment.created |
Scheduled deployment created |
deployment.updated |
Deployment properties changed (e.g. schedule edited) |
deployment.paused |
Deployment paused - by request, or automatically when a scheduled run fails with a non-recoverable error (archived agent, missing environment). Recoverable failures, including rate limits, do not auto-pause. |
deployment.unpaused |
Deployment unpaused; schedule resumes |
deployment.archived |
Deployment archived - directly, or as a result of agent archival/deletion |
deployment.deleted |
Deployment permanently deleted - no object left to fetch; treat the event itself as final |
deployment_run.started |
A scheduled run started. Manual runs do not emit deployment_run.* events. |
deployment_run.succeeded |
Scheduled run created its session. Same data.id (the run ID) as the run's .started event - fetch the deployment run for its session_id, then subscribe to the session events to follow the work. |
deployment_run.failed |
Scheduled run did not create a session. Same data.id as the run's .started event - fetch the deployment run for error.type / error.message. |
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 one. No object left to fetch; treat the event itself as final |
memory_store.created |
Memory store created - by you, or by an Anthropic-operated process that clones one of your 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 one. Cascades to its memories and versions without per-memory events - this single event is the signal. No object left to fetch; treat it as final |
There is deliberately no
memory_store.updated. Individual memories and memory versions emit no webhook events at all, and neither do an environment's self-hosted work items. If you need per-memory change tracking, poll the memory-versions endpoints (shared/managed-agents-memory.md).
These are webhook
data.typevalues - a separate namespace from SSE event types (session.status_idle,span.outcome_evaluation_end, etc. inshared/managed-agents-events.md). Don't reuse SSE constants in webhook handlers.
Delivery behavior & pitfalls
- Duplicates. An endpoint can receive the same event more than once; every attempt carries the same top-level
event.id(= thewebhook-idheader). Dedupe on it. - Subscription scope. An event reaches only endpoints subscribed to its type at the moment it is emitted. An event emitted while nothing was subscribed is never delivered, and subscribing later does not backfill - subscribe before you need the type.
- No ordering guarantee. Events are not delivered in occurrence order:
session.status_idledmay arrive beforesession.outcome_evaluation_ended, and a.deletedcan arrive before the.archivedfor the same resource. Drive state from the resource you fetch, not from arrival order. - Retries: up to three attempts per endpoint per event, with jittered exponential backoff between 5 and 120 seconds. A response that triggers auto-disable is never retried. After the last attempt fails the event is dropped - not queued, and with no signal that it was lost. Webhooks are not a durable log: if you must observe every transition, reconcile by listing or fetching the resource.
webhook-timestampis re-stamped on every attempt, so retries don't fail the SDK's five-minute freshness check. It times the delivery attempt; use the payload'screated_atfor when the event occurred.- Auto-disable - three triggers, each setting
disabled_reason, all reversible from Console (events emitted while disabled are not replayed):- A
3xxresponse. Redirects are never followed; disables immediately, on the first attempt. Reason:auto-disabled: endpoint URL returned a redirect (3xx). - The URL resolves to a non-public IP at connect time. Disables immediately. Reason:
auto-disabled: endpoint URL resolved to an invalid address. - Continuous failure for a sustained period. Reason:
auto-disabled after sustained delivery failures. The trigger is duration, not a delivery count - a single2xxresets the window, so one flaky event can't disable the endpoint.
- A
- Thin payload is intentional. Don't expect
stop_reason(list the session's events for that - the session object has nostop_reasonfield),outcome_evaluations, credential secrets, etc. on the webhook body - fetch the resource.