Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Implement event-driven patterns with Claude API: streaming SSE events, Message Batches callbacks, and async processing architectures. Use when building real-time Claude integrations or processing batch results. Trigger with phrases like "anthropic events", "claude streaming events", "anthropic async processing", "claude batch callbacks".
.claude/skills/jeremylongshore-anth-webhooks-events/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-15 | ✗→✓ | ▲ Improved | 323% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 166% | 0% |
| case-14 | ✓→✓ | = Same ✓ | 10% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 13% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 19% | 0% |
The Claude API does not use traditional webhooks. Instead it provides two event-driven patterns: Server-Sent Events (SSE) for real-time streaming and the Message Batches API for async bulk processing. This skill covers both.
pythonimport anthropic client = anthropic.Anthropic() # Process each SSE event type with client.messages.stream( model="claude-sonnet-4-20250514", max_tokens=1024, messages=[{"role": "user", "content": "Explain microservices."}] ) as stream: for event in stream: match event.type: case "message_start": print(f"Started: {event.message.id}") case "content_block_start": if event.content_block.type == "tool_use": print(f"Tool call: {event.content_block.name}") case "content_block_delta": if event.delta.type == "text_delta": print(event.delta.text, end="", flush=True) elif event.delta.type == "input_json_delta": print(event.delta.partial_json, end="") case "message_delta": print(f"\nStop: {event.delta.stop_reason}") print(f"Output tokens: {event.usage.output_tokens}") case "message_stop": print("[Complete]")
| Event | When | Key Data | |-------|------|----------| | message_start | Stream begins | message.id, message.model, message.usage.input_tokens | | content_block_start | New block begins | content_block.type (text or tool_use), index | | content_block_delta | Incremental content | delta.text or delta.partial_json | | content_block_stop | Block finishes | index | | message_delta | Message-level update | delta.stop_reason, usage.output_tokens | | message_stop | Stream complete | (empty) | | ping | Keepalive | (empty) |
python# Submit batch (up to 100K requests, 50% cheaper) batch = client.messages.batches.create( requests=[ { "custom_id": f"doc-{i}", "params": { "model": "claude-sonnet-4-20250514", "max_tokens": 1024, "messages": [{"role": "user", "content": f"Summarize: {doc}"}] } } for i, doc in enumerate(documents) ] ) # Poll for completion import time while True: status = client.messages.batches.retrieve(batch.id) if status.processing_status == "ended": break counts = status.request_counts print(f"Processing: {counts.processing} | Done: {counts.succeeded} | Errors: {counts.errored}") time.sleep(30) # Stream results for result in client.messages.batches.results(batch.id): if result.result.type == "succeeded": print(f"[{result.custom_id}]: {result.result.message.content[0].text[:100]}") else: print(f"[{result.custom_id}] ERROR: {result.result.error}")
python# Use queues to decouple Claude requests from user-facing endpoints from redis import Redis from rq import Queue redis = Redis() queue = Queue(connection=redis) def process_with_claude(prompt: str, callback_url: str): """Background job for async Claude processing.""" client = anthropic.Anthropic() msg = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, messages=[{"role": "user", "content": prompt}] ) # Notify your system via internal callback import requests requests.post(callback_url, json={ "text": msg.content[0].text, "usage": {"input": msg.usage.input_tokens, "output": msg.usage.output_tokens} }) # Enqueue from your API handler job = queue.enqueue(process_with_claude, prompt="...", callback_url="https://internal/callback")
| Issue | Cause | Fix | |-------|-------|-----| | Stream disconnects | Network timeout | Reconnect and re-request (responses are not resumable) | | Batch expired | Not processed in 24h | Resubmit the batch | | errored results | Individual request was invalid | Check result.error.message per request |
side_effects=0.message_stop. Never assume a disconnected stream is resumable.Produce an event-processing receipt with correlation/batch ID, event types and counts, succeeded/errored/expired counts, retry/dead-letter counts, callback authentication result, idempotency result, side_effects=0 for tests, canary status, rollback reference, and cleanup status. Include error classes, not raw payloads.
Submit two synthetic fixtures with custom IDs demo-001 and demo-002, consume each result twice, and assert one stored result per ID. A safe receipt can state batch=redacted; succeeded=2; duplicates_suppressed=2; callbacks_authenticated=true; side_effects=0; cleanup=verified without including document text or generated output.
For performance optimization, see anth-performance-tuning.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-14 | pass→pass | 13,260 | 7,771 | -41% | 1 | 1 | 0% | 2,720 | 2,988 | +10% | 0 | 0 | — |
case-01 | fail→fail | 11,962 | 8,176 | -32% | 1 | 1 | 0% | 2,889 | 3,220 | +11% | 0 | 0 | — |
case-02 | fail→fail | 14,882 | 10,043 | -33% | 1 | 1 | 0% | 3,041 | 3,641 | +20% | 0 | 0 | — |
case-03 | fail→fail | 14,046 | 11,350 | -19% | 1 | 1 | 0% | 3,032 | 3,697 | +22% | 0 | 0 | — |
case-04 | pass→pass | 9,009 | 5,336 | -41% | 1 | 1 | 0% | 1,956 | 2,220 | +13% | 0 | 0 | — |
case-05 | pass→pass | 9,128 | 5,150 | -44% | 1 | 1 | 0% | 1,931 | 2,307 | +19% | 0 | 0 | — |
case-06 | pass→pass | 9,572 | 5,126 | -46% | 1 | 1 | 0% | 1,975 | 2,332 | +18% | 0 | 0 | — |
case-07 | pass→pass | 4,966 | 2,853 | -43% | 1 | 1 | 0% | 935 | 1,870 | +100% | 0 | 0 | — |
case-08 | pass→pass | 10,613 | 7,604 | -28% | 1 | 1 | 0% | 2,583 | 2,939 | +14% | 0 | 0 | — |
case-09 | pass→pass | 11,605 | 7,109 | -39% | 1 | 1 | 0% | 2,560 | 2,943 | +15% | 0 | 0 | — |
case-10 | fail→fail | 12,547 | 11,139 | -11% | 1 | 1 | 0% | 2,144 | 3,429 | +60% | 0 | 0 | — |
case-11 | pass→pass | 11,915 | 8,300 | -30% | 1 | 1 | 0% | 2,156 | 2,815 | +31% | 0 | 0 | — |
case-12 | pass→pass | 6,586 | 3,837 | -42% | 1 | 1 | 0% | 1,255 | 2,027 | +62% | 0 | 0 | — |
case-13 | pass→pass | 10,348 | 7,597 | -27% | 1 | 1 | 0% | 1,919 | 2,958 | +54% | 0 | 0 | — |
case-15 | fail→pass | 2,032 | 2,301 | +13% | 1 | 1 | 0% | 367 | 1,551 | +323% | 0 | 0 | — |
case-16 | pass→pass | 2,619 | 1,668 | -36% | 1 | 1 | 0% | 454 | 1,501 | +231% | 0 | 0 | — |
case-17 | fail→pass | 3,648 | 2,284 | -37% | 1 | 1 | 0% | 620 | 1,648 | +166% | 0 | 0 | — |
case-18 | pass→pass | 3,591 | 1,982 | -45% | 1 | 1 | 0% | 592 | 1,565 | +164% | 0 | 0 | — |
case-19 | pass→pass | 8,388 | 3,239 | -61% | 1 | 1 | 0% | 1,511 | 1,867 | +24% | 0 | 0 | — |
case-20 | pass→pass | 5,879 | 2,881 | -51% | 1 | 1 | 0% | 1,247 | 1,756 | +41% | 0 | 0 | — |
case-21 | pass→pass | 12,782 | 10,257 | -20% | 1 | 1 | 0% | 2,707 | 3,484 | +29% | 0 | 0 | — |
case-22 | pass→pass | 9,880 | 9,773 | -1% | 1 | 1 | 0% | 1,979 | 3,405 | +72% | 0 | 0 | — |
case-23 | pass→pass | 14,326 | 10,991 | -23% | 1 | 1 | 0% | 3,120 | 3,817 | +22% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +9 percentage points is the difference between those two pass rates over the 23 comparable cases.
The publisher has shipped newer versions since this run, so these numbers describe v1, not the version currently listed.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.