Sessions are long-running interactions. While most real-time interactions happen through the SSE event stream, 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.
Visit Manage > Webhooks in Console.
A webhook endpoint consists of:
data.type values this endpoint receives. An endpoint only receives events it's subscribed to, plus test events (see Delivery behavior).whsec_-prefixed secret generated at creation. It's shown only once, so store it securely to verify webhook deliveries.Every delivery carries an X-Webhook-Signature header. 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 five minutes old.
Set ANTHROPIC_WEBHOOK_SIGNING_KEY to the whsec_-prefixed secret shown at endpoint creation.
Parse the body, switch on data.type, and fetch the resource by ID. Return any 2xx to acknowledge. Anything else (including 3xx) counts as a failure and triggers a retry.
Every event payload has the same structure, including the event type, identifier, and timestamp of when the object was created.
{
"type": "event",
"id": "event_01ABC...",
"created_at": "2026-03-18T14:05:22Z",
"data": {
"type": "session.status_idled",
"id": "sesn_01XYZ...",
"organization_id": "8a3d2f1e-...",
"workspace_id": "c7b0e4d9-..."
}
}The top-level event.id is unique per event, not per delivery. If you receive the same event.id twice, it's a retry and you can discard it.
session.status_idled may arrive before session.outcome_evaluation_ended even if the outcome was produced first. Use the created_at timestamp to sort if ordering matters.event.id.3xx is treated as a failure. If your endpoint moves, update the URL in Console.disabled with a machine-readable disabled_reason after roughly 20 consecutive failed deliveries, or immediately if the hostname resolves to a private IP or the endpoint returns a redirect. Re-enable manually in Console after resolving the issue.Was this page helpful?
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 "", 200if event.data.type == "session.status_idled":
session = client.beta.sessions.retrieve(event.data.id)
notify_user(session)
return "", 204