paybondpaybond
Sign in

Protect Stripe payments from agents

Guard Stripe PaymentIntent and charge tools with Paybond middleware — authorize spend before side effects, submit evidence after success, and keep tenant scope on the server.

Your agent should not call Stripe to charge a card or capture a PaymentIntent without a bounded spend boundary and an audit trail. Paybond sits at the tool execution boundary — not inside Stripe's SDK — so Harbor verifies the operation and amount before your handler runs, then auto-submits evidence when the charge succeeds.

Paybond guards tools, not Stripe webhooks

Use Paybond middleware on the tool that creates or captures payments. Keep Stripe webhook handlers for funding signals separate from tool-completion evidence. See How intent funding works.

Try it (no Stripe credentials)

Terminal
Terminal commandSwipe to inspect long lines
paybond login
paybond agent sandbox smoke \
  --operation payments.charge_customer \
  --requested-spend-cents 2500 \
  --evidence-preset cost_and_completion \
  --result-body '{"status":"completed","cost_cents":2500,"payment_intent_id":"pi_smoke"}' \
  --format table

Scaffold a Stripe-aware policy

Presets are starting points — fork a local file for your charge tools:

Terminal
Terminal commandSwipe to inspect long lines
paybond policy init --preset saas --out paybond.policy.yaml

Edit paybond.policy.yaml to match your Stripe tool names:

version: 1
name: stripe-agent-v1
default_deny: true

tools:
  payments.charge_customer:
    side_effecting: true
    max_spend_cents: 50000
    evidence_preset: cost_and_completion

  payments.list_invoices:
    side_effecting: false

intent:
  allowed_tools:
    - payments.charge_customer
  budget:
    currency: usd
    max_spend_usd: 500

Validate before deploy:

Terminal
Terminal commandSwipe to inspect long lines
paybond policy validate-tools --file paybond.policy.yaml --local-only

Wire middleware (agent-agnostic)

Paybond's default path works with any orchestrator that exposes { name, execute } tools — including a custom Stripe wrapper:

paybond-session.ts

Code exampleSwipe to inspect long lines
import Stripe from "stripe";
import { Paybond } from "@paybond/kit";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const paybond = await Paybond.open({ apiKey: process.env.PAYBOND_API_KEY! });

async function chargeCustomer(args: {
  customerId: string;
  amountCents: number;
  currency: string;
}) {
  const pi = await stripe.paymentIntents.create({
    amount: args.amountCents,
    currency: args.currency,
    customer: args.customerId,
    confirm: true,
    automatic_payment_methods: { enabled: true },
  });
  return { status: pi.status, cost_cents: args.amountCents, payment_intent_id: pi.id };
}

const instrumented = await paybond.instrument({
  policy: "./paybond.policy.yaml",
  tools: {
    "payments.charge_customer": chargeCustomer,
    "payments.list_invoices": listInvoices,
  },
});

// Per session (production): const runtime = await instrumented.bind({ intentId, capabilityToken });
// Sandbox quickstart: paybond.agent({ policy: "./paybond.policy.yaml", tools: { ... } })

Production checklist

  1. Configure settlement rails in the console — Configure settlement rails.
  2. Create and fund an intent — Fund intents by rail.
  3. Bind middleware per request with instrumented.bind({ intentId, capabilityToken }) or a lazy context provider.
  4. Never pass tenant ids from unauthenticated client input — tenant scope comes from the Paybond API key.

Developer reference: /docs/kit/agent-middleware.