paybondpaybond
Sign in

Recipe · Payments protect

Charge customers from agents—without unbounded Stripe spend

Give agents spending limits, PaymentIntent binding, and signed stripe_charge evidence for Stripe tools — without rewriting your payment stack.

  1. 1Wire
  2. 2Authorize
  3. 3Smoke

You'll build

Bounded Stripe tools with spend verify and signed stripe_charge evidence

Before you start

  • paybond login
  • stripe-commerce smoke
  • ~3 min

In short: give the agent a spend limit before it can call Stripe, and get a receipt after. 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 — Harbor verifies the operation and amount before your handler runs.

  • Spending limits

    Harbor verifies operation and amount before Stripe side effects run.

  • Audit evidence

    Signed stripe_charge digests bind the tool result to the intent.

  • PaymentIntent binding

    Metadata ties agent charges to your Harbor intent and tenant.

  • Works with Kit

    Guard charge tools with paybond.instrument() — any orchestrator.

Why not just Stripe?

Stripe APIs create PaymentIntents. They do not enforce per-agent spend limits, per-operation permission checks (capability tokens), or a signed receipt of what ran. Paybond adds that guardrail layer without replacing the Stripe SDK or ACS.

Stripe API alone versus Paybond-guarded Stripe charges
  • Creates PaymentIntent

    Stripe API alone
    Yes — Stripe SDK / ACS
    With Paybond
    Yes — same Stripe surfaces, after Harbor authorize
  • Spending limits

    Stripe API alone
    Radar / account rules; no per-agent Harbor budget
    With Paybond
    Per-call and intent budgets enforced before the tool runs
  • Audit evidence

    Stripe API alone
    Stripe Dashboard payment history
    With Paybond
    Signed evidence digests + settlement receipts
  • Agent authorization

    Stripe API alone
    Secret key / restricted key access
    With Paybond
    Capability token + policy decision at verify time
  • Settlement receipts

    Stripe API alone
    Stripe charge / PI records
    With Paybond
    Evidence evaluation + optional Agent Receipt Standard

How it works

Agent charge guarded by Paybond: verify spend, call Stripe, fund via webhook separately, submit evidence, then settle.

How it works

  1. Agent

    Calls payments.charge_customer (or your Stripe wrapper)

  2. Paybond Guard

    Harbor authorize before your handler runs

    • Verify spend and operation
    • Generate / bind intent
    • Issue capability token
  3. Stripe API

    Create / confirm PaymentIntent with binding metadata

  4. Webhook

    payment_intent.succeeded funds the intent (separate from tool evidence)

  5. Evidence

    Submit stripe_charge after tool success

  6. Settlement

    Capture / release per settlement rails

Agent charge guarded by Paybond: verify spend, call Stripe, fund via webhook separately, submit evidence, then settle.

3-minute quickstart

Smoke the sandbox contract with the stripe-commerce preset — no Stripe credentials required:

Terminal
Terminal commandSwipe to inspect long lines
paybond login
paybond agent sandbox smoke \
  --preset stripe-commerce \
  --format table

Optional policy scaffold: paybond policy init --preset stripe-commerce.

When the smoke succeeds you should see:

  • ✓ Spend approved
  • ✓ Charge completed
  • ✓ Evidence verified (stripe_charge)

Explicit smoke (same contract as the preset)

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

What success looks like

Example payment status after a Paybond-guarded Stripe charge: approved spend, requested amount, and verified stripe_charge evidence.

What success looks like

Sandbox charge · illustrative

Settled path
Operation
payments.charge_customer
Status
Approved
Requested
$25.00
Evidence
Verified
Preset
stripe_charge

Scaffold a Stripe-aware policy

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

That writes a local policy with payments.charge_customer (stripe_charge evidence) and a read-only payments.list_invoices tool. Adjust budgets and tool names before deploy if needed:

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

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

  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

Paybond's default path works with any orchestrator that exposes { name, execute } tools — including a custom Stripe wrapper. Tenant and intent IDs come from the Paybond session binding — never from unauthenticated tool args.

paybond-session.ts

TS
Code exampleSwipe to inspect long lines
import Stripe from "stripe";
import {
  Paybond,
  buildPaybondStripeMetadata,
  mapStripeToolResultToEvidence,
} 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;
  /** Trusted invoice id — load the amount server-side; do not take free-form dollars from the model. */
  invoiceId: string;
  tenantId: string;
  intentId: string;
}) {
  const invoice = await stripe.invoices.retrieve(args.invoiceId);
  if (invoice.customer !== args.customerId) {
    throw new Error("invoice does not belong to customer");
  }
  const amountCents = invoice.amount_due;
  if (amountCents <= 0) {
    throw new Error("invoice has nothing due");
  }

  const metadata = buildPaybondStripeMetadata({
    tenantId: args.tenantId,
    intentId: args.intentId,
    rail: "stripe_connect",
  });

  const pi = await stripe.paymentIntents.create({
    amount: amountCents,
    currency: invoice.currency,
    customer: args.customerId,
    confirm: true,
    metadata,
    automatic_payment_methods: { enabled: true },
  });

  const toolResult = {
    status: pi.status,
    cost_cents: amountCents,
    invoice_id: invoice.id,
    payment_intent_id: pi.id,
    charge_id:
      typeof pi.latest_charge === "string"
        ? pi.latest_charge
        : pi.latest_charge?.id,
  };

  mapStripeToolResultToEvidence(toolResult, { preset: "stripe_charge" });
  return toolResult;
}

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: { ... } })

mapStripeToolResultToEvidence rejects Stripe funding webhook envelopes (payment_intent.succeeded event shapes). Those fund Harbor intents — they are not tool-completion evidence.

Binding metadata

When your app creates Stripe PaymentIntents (ACS checkout or guarded SDK tools), attach Harbor binding metadata so Gateway webhook preflight can resolve tenant_id and paybond_intent_id. Use Kit helpers — never trust unauthenticated client input for those ids.

Attach Harbor binding metadata on every app-owned PaymentIntent create so Gateway webhook preflight can resolve tenant scope without trusting the agent.

Binding metadata

  1. Create PaymentIntent

    ACS checkout or guarded Stripe SDK tool

  2. Inject metadata

    buildPaybondStripeMetadata

    • tenant_id
    • paybond_intent_id
    • paybond_settlement_rail (optional)
  3. Webhook preflight

    Gateway resolves intent + tenant from metadata

Attach Harbor binding metadata on every app-owned PaymentIntent create so Gateway webhook preflight can resolve tenant scope without trusting the agent.

paybond-session.ts

TS
Code exampleSwipe to inspect long lines
import { buildPaybondStripeMetadata } from "@paybond/kit";

// tenantId / intentId from authenticated Paybond session — not from the browser
const metadata = buildPaybondStripeMetadata({
  tenantId: session.tenantId,
  intentId: session.intentId,
  rail: "stripe_connect", // optional: stripe_connect | stripe_ach_debit
});

Canonical keys match Harbor webhook preflight: tenant_id, paybond_intent_id, and optional paybond_settlement_rail.

Production checklist

Production checklist for Paybond-guarded Stripe agent charges.

Production checklist

Works with

Works with

  • Stripe
  • OpenAI
  • Anthropic
  • MCP

Ready to test?

Developer reference: /docs/kit/agent-middleware. Runnable template: paybond init --template paybond-stripe-agent-demo.