LangChain · Stripe · Agent security

LangChain Stripe API key: wiring it up safely with spend caps

Giving a LangChain agent a Stripe API key is ten lines of code. Making sure a stuck loop doesn't empty your Stripe account is the part the documentation skips. This page covers StripeTool setup, the three gaps Stripe's restricted keys leave open, and the proxy pattern that closes all three.

TL;DR

LangChain's StripeTool and community Stripe integrations give your agent a raw Stripe API key. Stripe's restricted keys reduce the blast radius by limiting endpoint access — but they have three gaps: no per-day dollar cap, no mid-run revoke without rotating your production key, and no per-call audit log with agent context. A vault key proxy closes all three by sitting between the agent and Stripe: the agent holds a scoped vault key, the proxy enforces the real key plus the policy, and you can revoke or cap without touching production code.

Setting up StripeTool in LangChain

The fastest path to a LangChain agent that can call Stripe is langchain-community's StripeTool or a custom BaseTool wrapping the stripe Python SDK. The setup looks like this:

import stripe
from langchain.tools import BaseTool

stripe.api_key = os.environ["STRIPE_SECRET_KEY"]

class ChargeCustomerTool(BaseTool):
    name = "charge_customer"
    description = "Charge a Stripe customer. Input: customer_id, amount_cents, currency."

    def _run(self, customer_id: str, amount_cents: int, currency: str = "usd") -> str:
        charge = stripe.PaymentIntent.create(
            amount=amount_cents,
            currency=currency,
            customer=customer_id,
        )
        return f"Created PaymentIntent {charge.id}"

Hand that tool to an agent executor and the agent can issue charges on demand. It works in a demo. The production problem is STRIPE_SECRET_KEY: it's a long-lived credential, it covers all Stripe endpoints, and it has no spend cap.

What Stripe restricted keys do (and where they stop)

The first thing most engineers reach for is a Stripe restricted key — a key you create in the Stripe Dashboard with a curated set of endpoint permissions. The endpoint allows/denies are genuinely useful: you can give the agent write access to payment_intents and customers without giving it access to refunds.create, account.update, or payout.create.

Three gaps remain after restricting the key:

GapWhat happensWhy it matters
No per-day $ cap A stuck loop charges $X every N seconds until you notice Stripe doesn't surface a "max spend per day" control on restricted keys
No mid-run revoke Revoking the key breaks every other consumer using it; key rotation takes 1–3 min to propagate The only agent-specific stop requires a production secret rotation
No agent-context audit log Stripe logs requests by IP; you can't join on agent_run_id Forensics after an incident requires manual timeline reconstruction

The vault key pattern

The vault key pattern sits a proxy between your LangChain agent and Stripe. Instead of STRIPE_SECRET_KEY, you set STRIPE_BASE_URL=https://proxy.keybrake.com/stripe/v1 and pass a scoped vault_key_xxx. The proxy holds the real Stripe secret and enforces the policy you configured on the vault key.

import stripe

# One-line change: base_url points to the proxy
stripe.api_key = os.environ["VAULT_KEY"]          # vault_key_xxx
stripe.base_url = "https://proxy.keybrake.com/stripe/v1"

# The rest of your agent code is unchanged
class ChargeCustomerTool(BaseTool):
    name = "charge_customer"
    description = "Charge a Stripe customer with the proxied key."

    def _run(self, customer_id: str, amount_cents: int, currency: str = "usd") -> str:
        charge = stripe.PaymentIntent.create(
            amount=amount_cents,
            currency=currency,
            customer=customer_id,
        )
        return f"Created PaymentIntent {charge.id}"

The vault key policy you configure on your Keybrake dashboard:

{
  "vendor": "stripe",
  "daily_usd_cap": 500,
  "allowed_endpoints": [
    "POST /v1/payment_intents",
    "GET /v1/customers/*"
  ],
  "expires_in": "8h",
  "agent_run_id": "run_abc123"
}

With that policy, the agent can charge up to $500/day total on payment_intents only. Revoking the vault key takes effect on the next request — sub-second, no production key rotation required.

Three LangChain patterns where this matters most

1. ReAct agents with Stripe tools in the tool set. These agents reason step-by-step and can call the same tool repeatedly in one reasoning chain. A thought like "I should try charging again in case the first one failed" results in double-charges without a per-call idempotency check, and no per-day cap means the loop runs until the model hits its context limit.

2. Retrieval-augmented agents that look up customer IDs. If the retrieval step returns a wrong customer ID and the agent charges the wrong customer, the audit log (with agent_run_id and customer_id per call) is what makes the forensics possible.

3. LangGraph multi-step workflows. A LangGraph graph might have a "billing" node that fires a Stripe charge as a step in a longer workflow. The vault key scopes that node's access to exactly the endpoints it needs; the daily cap prevents a graph re-run bug from doubling charges.

How Keybrake fits

Keybrake is the proxy. You issue a vault key per agent run, attach a policy, and the one-line base_url change routes the agent through the proxy. Spend caps, allowlists, mid-run revoke, and a queryable audit log with agent_run_id are all included. The Free tier covers 1,000 proxied requests/month with one vendor; the Hobby tier ($29/month) adds all vendors and 30-day log retention.

Get early access

Related questions

Does the one-line base_url change work with all Stripe SDK methods?

Yes — the Stripe Python, Node, Ruby, and Go SDKs all respect a base_url or equivalent override. The proxy speaks the Stripe HTTP wire protocol exactly; the SDK doesn't know it's talking to a proxy. The only exception is Stripe-signed webhook payloads, which come from Stripe directly (not through the proxy) — those continue to use your real signing secret.

What happens if the agent hits the daily cap?

The proxy returns a 429 Too Many Requests with a Retry-After header set to midnight UTC of the current day. LangChain's default tool retry logic will surface this as a tool error; the agent sees "daily spend cap reached" in the error message and can decide whether to tell the user or abort. You can also configure a Slack/webhook alert to fire when the cap is reached — before the 429 happens, on the call that would exceed the cap.

Can I use this with LangChain's OpenAI functions agent instead of ReAct?

Yes. The proxy is at the Stripe HTTP layer, not the LangChain agent layer — it doesn't care which agent architecture routes the call. Whether you're using a ReAct agent, an OpenAI functions agent, a LangGraph graph, or a raw AgentExecutor, as long as the Stripe SDK's base_url points at the proxy, all calls go through policy enforcement.

Further reading