Esta página se muestra en inglés. Una traducción al español está en curso.
SkillsMay 8, 2026·4 min de lectura

Stripe Webhook → Anthropic Tool Pattern for Async Agents

Convert Stripe webhook events (payment, dispute, refund) into Claude tool calls so agents react to real-time payment lifecycle events.

Introducción

This skill turns Stripe webhook events into Anthropic tool-call inputs so a Claude agent reacts to real payment lifecycle events: payment succeeded, dispute opened, subscription canceled, refund issued. The pattern is a tiny FastAPI/Express endpoint that verifies the Stripe signature, transforms the event into a structured tool call, and dispatches the call against your existing agent. Best for: support agents that triage disputes, billing chatbots that confirm payments, churn-intervention agents reacting to subscription events. Works with: Anthropic API, OpenAI Tool Use, any agent that takes tool inputs. Setup time: 15 minutes.


FastAPI endpoint

import os, stripe
from anthropic import Anthropic
from fastapi import FastAPI, Request, HTTPException

app = FastAPI()
stripe.api_key = os.environ["STRIPE_SECRET_KEY"]
WEBHOOK_SECRET = os.environ["STRIPE_WEBHOOK_SECRET"]
claude = Anthropic()

EVENT_TO_TOOL = {
    "charge.dispute.created":         "handle_dispute",
    "invoice.payment_failed":         "retry_failed_payment",
    "customer.subscription.deleted":  "churn_outreach",
    "charge.refunded":                "log_refund",
}

@app.post("/stripe/webhook")
async def webhook(request: Request):
    payload = await request.body()
    sig = request.headers.get("stripe-signature")
    try:
        event = stripe.Webhook.construct_event(payload, sig, WEBHOOK_SECRET)
    except (ValueError, stripe.error.SignatureVerificationError):
        raise HTTPException(400, "bad signature")

    tool_name = EVENT_TO_TOOL.get(event["type"])
    if not tool_name:
        return {"status": "ignored"}

    obj = event["data"]["object"]
    msg = claude.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=1024,
        tools=[{
            "name": tool_name,
            "description": f"Triggered by Stripe event {event['type']}",
            "input_schema": {"type": "object", "properties": {
                "stripe_id": {"type": "string"},
                "amount":    {"type": "integer"},
                "customer":  {"type": "string"},
                "reason":    {"type": "string"},
            }},
        }],
        messages=[{
            "role": "user",
            "content": f"A Stripe {event['type']} event occurred. Decide on next action.\n\n{obj}",
        }],
    )
    return {"status": "dispatched", "claude_response": msg.content}

Production hardening

  • Signature verification is mandatory — never skip construct_event. Without it, anyone can POST fake events.
  • Idempotency — Stripe retries webhooks for 3 days on non-2xx. Store event["id"] in Redis with 7-day TTL; skip if seen.
  • Async dispatch — return 200 within 10s. Push the Claude call to a queue (Celery / Inngest / SQS) if it takes longer.
  • Replay attacksconstruct_event enforces 5-minute timestamp tolerance. Don't widen it.

FAQ

Q: Why not have Claude call Stripe directly with the Agent Toolkit? A: For synchronous user-driven flows, do that. This pattern is for async events that happen without a user prompt — disputes opened by the cardholder, subscriptions canceled by Stripe Smart Retries, payments failing on retry. The webhook is the trigger; Claude is the policy engine.

Q: How do I scope which events fire Claude? A: Whitelist in the EVENT_TO_TOOL map. Stripe will keep delivering all subscribed events but you only spend Claude tokens on the ones you map. Subscribe selectively at dashboard.stripe.com/webhooks too.

Q: What about test mode? A: Use Stripe CLI: stripe trigger charge.dispute.created --forward-to localhost:8000/stripe/webhook. The CLI signs events with your test webhook secret and replays them locally.


Quick Use

  1. Add a Stripe webhook endpoint at dashboard.stripe.com/webhooks pointing at /stripe/webhook
  2. Set STRIPE_WEBHOOK_SECRET, STRIPE_SECRET_KEY, ANTHROPIC_API_KEY
  3. Map events to tool names in EVENT_TO_TOOL

Intro

This skill turns Stripe webhook events into Anthropic tool-call inputs so a Claude agent reacts to real payment lifecycle events: payment succeeded, dispute opened, subscription canceled, refund issued. The pattern is a tiny FastAPI/Express endpoint that verifies the Stripe signature, transforms the event into a structured tool call, and dispatches the call against your existing agent. Best for: support agents that triage disputes, billing chatbots that confirm payments, churn-intervention agents reacting to subscription events. Works with: Anthropic API, OpenAI Tool Use, any agent that takes tool inputs. Setup time: 15 minutes.


FastAPI endpoint

import os, stripe
from anthropic import Anthropic
from fastapi import FastAPI, Request, HTTPException

app = FastAPI()
stripe.api_key = os.environ["STRIPE_SECRET_KEY"]
WEBHOOK_SECRET = os.environ["STRIPE_WEBHOOK_SECRET"]
claude = Anthropic()

EVENT_TO_TOOL = {
    "charge.dispute.created":         "handle_dispute",
    "invoice.payment_failed":         "retry_failed_payment",
    "customer.subscription.deleted":  "churn_outreach",
    "charge.refunded":                "log_refund",
}

@app.post("/stripe/webhook")
async def webhook(request: Request):
    payload = await request.body()
    sig = request.headers.get("stripe-signature")
    try:
        event = stripe.Webhook.construct_event(payload, sig, WEBHOOK_SECRET)
    except (ValueError, stripe.error.SignatureVerificationError):
        raise HTTPException(400, "bad signature")

    tool_name = EVENT_TO_TOOL.get(event["type"])
    if not tool_name:
        return {"status": "ignored"}

    obj = event["data"]["object"]
    msg = claude.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=1024,
        tools=[{
            "name": tool_name,
            "description": f"Triggered by Stripe event {event['type']}",
            "input_schema": {"type": "object", "properties": {
                "stripe_id": {"type": "string"},
                "amount":    {"type": "integer"},
                "customer":  {"type": "string"},
                "reason":    {"type": "string"},
            }},
        }],
        messages=[{
            "role": "user",
            "content": f"A Stripe {event['type']} event occurred. Decide on next action.\n\n{obj}",
        }],
    )
    return {"status": "dispatched", "claude_response": msg.content}

Production hardening

  • Signature verification is mandatory — never skip construct_event. Without it, anyone can POST fake events.
  • Idempotency — Stripe retries webhooks for 3 days on non-2xx. Store event["id"] in Redis with 7-day TTL; skip if seen.
  • Async dispatch — return 200 within 10s. Push the Claude call to a queue (Celery / Inngest / SQS) if it takes longer.
  • Replay attacksconstruct_event enforces 5-minute timestamp tolerance. Don't widen it.

FAQ

Q: Why not have Claude call Stripe directly with the Agent Toolkit? A: For synchronous user-driven flows, do that. This pattern is for async events that happen without a user prompt — disputes opened by the cardholder, subscriptions canceled by Stripe Smart Retries, payments failing on retry. The webhook is the trigger; Claude is the policy engine.

Q: How do I scope which events fire Claude? A: Whitelist in the EVENT_TO_TOOL map. Stripe will keep delivering all subscribed events but you only spend Claude tokens on the ones you map. Subscribe selectively at dashboard.stripe.com/webhooks too.

Q: What about test mode? A: Use Stripe CLI: stripe trigger charge.dispute.created --forward-to localhost:8000/stripe/webhook. The CLI signs events with your test webhook secret and replays them locally.


Source & Thanks

Pattern compiled from Stripe Webhooks + Anthropic Tool Use.

Stripe MIT-licensed, Anthropic SDK MIT-licensed.

🙏

Fuente y agradecimientos

Pattern compiled from Stripe Webhooks + Anthropic Tool Use.

Stripe MIT-licensed, Anthropic SDK MIT-licensed.

Discusión

Inicia sesión para unirte a la discusión.
Aún no hay comentarios. Sé el primero en compartir tus ideas.

Activos relacionados