SkillsMay 8, 2026·4 min read

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.

Agent ready

This asset can be read and installed directly by agents

TokRepo exposes the CLI command, metadata JSON, install plan, and raw content links so agents can judge fit, risk, and next actions.

Stage only · 17/100Stage only
Target
Claude Code
Kind
Skill
Install
Stage only
Trust
Trust: New
Entrypoint
Asset
CLI install command
npx tokrepo install a78e66e5-8945-4e63-a57b-bfd75011a20d --target codex
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.


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.

🙏

Source & Thanks

Pattern compiled from Stripe Webhooks + Anthropic Tool Use.

Stripe MIT-licensed, Anthropic SDK MIT-licensed.

Discussion

Sign in to join the discussion.
No comments yet. Be the first to share your thoughts.

Related Assets