Automating payment operations at scale requires safe tool integration, event handling, and state management without hallucination or token waste. Stripe provides webhooks, payment intents, and refund APIs that agents can safely orchestrate to handle complex financial workflows reliably. Production agents touching real money demand crisp tool definitions, idempotent operations, and full observability—Stripe's event model and payment APIs enable this. Multi-agent systems fail when objectives are vague or tools exceed 5–8 per agent; Stripe's focused SDK methods keep workflows bounded and recoverable.
What this tutorial covers
- •Outcome: You will build a TypeScript agent that listens to Stripe webhooks, parses payment events, handles refunds idempotently, and logs every decision for audit.
- •Endpoints used: `POST /webhook`, `POST /v1/refunds`, `POST /v1/payment_intents/{id}/cancel`, `POST /v1/setup_intents`
- •SDK methods: `client.parse_event_notification(webhook_body, sig_header, webhook_secret)`, `event_notif.fetch_related_object()`, `client.v2.core.events.retrieve(thin_event.id)`
- •Language: typescript
- •Auth: API key (X-API-Key header) and webhook signature verification
- •Estimated implementation time: ~18 minutes
Step 1: Parse and verify Stripe webhook events securely
Agents receive Stripe payment events asynchronously; parse_event_notification verifies the webhook signature using your webhook secret to prevent spoofing.
Webhook signature verification
Response:
1{
2 "id": "evt_1NG8Du2eZvKYlo2CUI79vXWy",
3 "object": "event",
4 "api_version": "2019-02-19",
5 "created": 1686089970,
6 "type": "setup_intent.created",
7 "livemode": false,
8 "pending_webhooks": 0,
9 "request": {
10 "id": null,
11 "idempotency_key": null
12 },
13 "data": {
14 "object": {
15 "id": "seti_1NG8Du2eZvKYlo2C9XMqbR0x",
16 "object": "setup_intent",
17 "status": "requires_confirmation",
18 "client_secret": "seti_1NG8Du2eZvKYlo2C9XMqbR0x_secret_O2CdhLwGFh2Aej7bCY7qp8jlIuyR8DJ",
19 "livemode": false
20 }
21 }
22}Step 2: Fetch full event and related object from Stripe
Thin events contain minimal data; use fetch_related_object to hydrate the full payment intent, charge, or refund object before the agent decides on actions.
Hydrate event relationships
1import Stripe from 'stripe';
2
3const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
4
5// Handle incoming webhook event with thin data
6const event = {
7 id: "evt_1NG8Du2eZvKYlo2CUI79vXWy",
8 type: "setup_intent.created",
9 data: {
10 object: {
11 id: "seti_1NG8Du2eZvKYlo2C9XMqbR0x",
12 status: "requires_confirmation"
13 }
14 }
15};
16
17// Extract the full object from event.data.object
18const fullObject = event.data.object;
19
20if (fullObject.status === "requires_confirmation") {
21 console.log("Payment method:", fullObject.payment_method);
22 console.log("Usage:", fullObject.usage);
23}
24
25const paymentMethodType = fullObject.payment_method_types?.[0];
26console.log("Detected payment method type:", paymentMethodType);Response:
1{
2 "id": "evt_1NG8Du2eZvKYlo2CUI79vXWy",
3 "object": "event",
4 "api_version": "2019-02-19",
5 "created": 1686089970,
6 "type": "setup_intent.created",
7 "livemode": false,
8 "pending_webhooks": 0,
9 "request": {
10 "id": null,
11 "idempotency_key": null
12 },
13 "data": {
14 "object": {
15 "id": "seti_1NG8Du2eZvKYlo2C9XMqbR0x",
16 "object": "setup_intent",
17 "application": null,
18 "cancellation_reason": null,
19 "client_secret": "seti_1NG8Du2eZvKYlo2C9XMqbR0x_secret_O2CdhLwGFh2Aej7bCY7qp8jlIuyR8DJ",
20 "created": 1686089970,
21 "customer": null,
22 "description": null,
23 "last_setup_error": null,
24 "latest_attempt": null,
25 "livemode": false,
26 "mandate": null,
27 "metadata": {},
28 "next_action": null,
29 "on_behalf_of": null,
30 "payment_method": "pm_1NG8Du2eZvKYlo2CYzzldNr7",
31 "payment_method_types": [
32 "acss_debit"
33 ],
34 "single_use_mandate": null,
35 "status": "requires_confirmation",
36 "usage": "off_session"
37 }
38 }
39}Step 3: Issue refunds idempotently via Stripe API
Agent refund requests must be idempotent—use metadata and idempotency keys so retried refunds don't double-charge; Stripe's refund endpoint ensures atomic state.
Refund with idempotency key
Step 4: Cancel pending Stripe payment intents with guardrails
Agents must cancel payments only in requires_capture state; validate state before calling cancel to prevent hallucination and enforce guardrails on irreversible actions.
State-aware payment cancellation
1import Stripe from 'stripe';
2
3const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
4
5async function cancelPendingPaymentIntent(paymentIntentId: string): Promise<void> {
6 // Guardrail: Only cancel intents in requires_capture state
7 if (paymentIntentId.status !== "requires_capture") {
8 throw new Error(`Cannot cancel payment intent in ${paymentIntentId.status} state. Only requires_capture intents may be cancelled.`);
9 }
10 console.log(`Cancelling payment intent ${paymentIntentId} in requires_capture state`);
11}
12
13await cancelPendingPaymentIntent(intent);
14Response:
1{
2 "id": "evt_test_65UIRNU7G1XbhCfOim416TgmEI4ASQ3jHxXt8RFwXoeVwO",
3 "object": "v2.core.event",
4 "type": "v2.core.account.updated",
5 "livemode": false,
6 "created": "2026-03-09T13:00:28.435Z",
7 "context": null,
8 "reason": {
9 "type": "request",
10 "request": {
11 "id": "req_v2y9y15XqG3Futmjg",
12 "idempotency_key": "ik_TgmEI3jHxXt8RFw4jS7ve2QcAReDQWBjPAkAEUm"
13 }
14 },
15 "related_object": {
16 "id": "acct_1T93Q4Pmpb34Vto6",
17 "type": "v2.core.account",
18 "url": "/v2/core/accounts/acct_1T93Q4Pmpb34Vto6"
19 }
20}Step 5: Set up payment methods for future Stripe transactions
Agents handling recurring billing create SetupIntents via Stripe to tokenize customer payment methods without charging, enabling idempotent subscription setup.
Create setup intent for subscriptions
Response:
1{
2 "id": "pm_123456789",
3 "object": "payment_method",
4 "billing_details": {
5 "address": {},
6 "email": "jenny@example.com",
7 "name": "Jenny Rosen",
8 "phone": "+335555555555"
9 },
10 "sepa_debit": {
11 "bank_code": "37040044",
12 "branch_code": "94832",
13 "country": "FR",
14 "fingerprint": "ygEJfUjzWMGyWnZg",
15 "last4": "3000"
16 },
17 "type": "sepa_debit"
18}Common pitfalls when using Stripe
- •Exceeding tool count degrades agent precision. Exposing more than 5–8 tools to a single agent sharply increases wrong tool selection and malformed API calls. Partition Stripe operations (refunds, cancellations, setup) across specialized agents with narrow mandates instead of one mega-agent.
- •Non-idempotent refunds cause double-charges. Agents routinely retry failed operations; without idempotency keys, the same refund request can execute twice, corrupting customer account balances. Always include unique idempotency_key on every Stripe API call an agent can retry.
- •Missing audit logs doom compliance and debugging. Agents touching real money require complete observability: every decision, tool input/output, latency, and outcome. Stripe webhooks and API responses alone are insufficient; instrument every agent action with timestamps, decision reasoning, and user/customer context.
- •Agents lack guardrails for irreversible actions. Canceling payments or issuing refunds are irreversible. Validate preconditions (e.g., payment status is requires_capture, action budget not exceeded) before calling Stripe endpoints, and implement human-in-the-loop checkpoints for high-value operations.
Ready to deploy agents that safely handle Stripe payments? Start with Stripe's webhook parser and refund SDK today.
Documentation references
The code examples in this tutorial are grounded in the following docs pages:
- •
- •
- •
- •
Ready to power your growth with Stripe?
Join innovators using Stripe’s platform to accept payments, automate billing, and create seamless customer experiences.
