paybondpaybond
Sign in

Fund intents on Stripe MPP

Guide to funding Paybond intents on stripe_mpp: charge vs Tempo session selection, Payment Auth header flow, sandbox behavior, and tenant-isolation boundaries.

In short: this is the funding path for one specific rail (Stripe MPP), so an agent's usage-metered spend can move machine-to-machine over HTTP. stripe_mpp is Paybond's Machine Payments Protocol settlement rail. Harbor advances funding on POST /harbor/intents/{id}/fund with HTTP Payment Auth semantics — 402 challenges, app-owned credentials, and Payment-Receipt on success.

This guide is the deep dive for stripe_mpp only. For the full multi-rail create → fund → capability flow, see Fund intents by rail. For tenant-admin rail setup, see Configure settlement rails.

Charge vs session — which helper?

MPP exposes two funding modes on the same rail literal. Pick the Kit helper that matches your spend shape — the helper selects the correct WWW-Authenticate: Payment … challenge.

ModeWhen to useTypeScriptPythonChallengeFunded when
ChargeOne-shot purchase, single tool call, fixed budget up frontfundWithMppChargefund_with_mpp_chargeintent="charge", method="stripe"funding.status = charge_succeeded
SessionStreaming metering — many tool calls under one pre-funded Tempo channelfundWithMppSessionfund_with_mpp_sessionintent="session", method="tempo"funding.status = deposit_confirmed

Charge maps to a Stripe-backed one-shot authorization (PaymentIntent / SPT-style credential). Paybond verifies the credential, confirms the charge, then mints capability_token.

Session opens a Tempo payment channel: one Paybond intent maps to one channel. Paybond verifies the open-session credential, confirms the on-chain reserve deposit (Tempo testnet in sandbox, mainnet in production), opens the channel, then mints capability_token. Subsequent tool spend consumes cumulative vouchers under the existing Paybond guardrail lifecycle — vouchers do not move the intent to released; evidence and settlement confirmation still gate payee payout.

Decision guide

Your workflowRecommended mode
Book one hotel, pay one invoice, single API purchaseCharge
Agent runs dozens of metered tool calls against one budget ceilingSession
Sandbox rehearsal of Payment Auth transport onlyCharge (simplest path)
Sandbox rehearsal of Tempo channel and voucher meteringSession (requires Tempo recipient configured)

For protocol background and status mapping, see the stripe_mpp support matrix row and MPP Payment Auth handshake.

Payment Auth header flow

Paybond never marks an MPP intent funded on create alone. Funding is a two-step (or poll) handshake on the same route.

Step-by-step

StepHTTPRequest headersResponse headersBody
1. ChallengePOST /harbor/intents/{id}/fundAuthorization: Bearer <api_key>402 with one or more WWW-Authenticate: Payment …; Cache-Control: no-storefunding.status = payment_required; funding.intent, method, challenge_id
2. CredentialPOST /harbor/intents/{id}/fund (retry)Authorization: Bearer <api_key> and x-paybond-payment-authorization: Payment <credential>200 with Payment-Receipt: …; Cache-Control: private or 202 (session pending) or 402 with application/problem+jsonstate = funded, capability_token on success

Gateway vs Harbor path

CallerCredential headerWhy
Kit / Gateway (POST /harbor/intents/{id}/fund)x-paybond-payment-authorization: Payment …Keeps Authorization: Bearer free for tenant API key auth
Direct HarborAuthorization: Payment …Standard Payment Auth scheme on the upstream request

Gateway translates x-paybond-payment-authorizationAuthorization: Payment when forwarding. Response headers (WWW-Authenticate, Payment-Receipt, Cache-Control, x402-compat payment-required / payment-response) pass through unchanged.

Invalid credentials

When a credential is rejected, expect:

  • 402 Payment Required
  • Content-Type: application/problem+json (RFC 9457 problem details)
  • Fresh WWW-Authenticate challenge(s)
  • Cache-Control: no-store

Fix the credential and retry with a new challenge — do not treat a 402 with a problem body as funded. Reuse the same idempotency-key and empty JSON body {} on safe retries.

Session deposit pending (202 Accepted)

Tempo session funding may return 202 Accepted when the open transaction is broadcast but the on-chain reserve deposit is not confirmed yet:

  • state remains open
  • funding.status = session_open_pending
  • funding.channel_id and last_settle_tx_hash are available for support debugging
  • Kit helpers poll /fund until capability_token is present or a terminal failure surfaces (PaybondMppFundingPendingError when polling exhausts)

Paybond also reconciles pending session opens in the background and mints the capability token when the deposit confirms. Poll with fresh recognition proofs; you do not need to resubmit the Payment Auth credential on each poll once the open credential was accepted.

Recognition proofs

Each /fund call needs a fresh AgentRecognitionProofV1 with purpose: "harbor.intent.fund" bound to the tenant and request envelope. Kit helpers issue proofs through your issueRecognitionProof callback. Proof nonces are single-use — credential retries need their own recognition proof.

Sandbox behavior

In sandbox, MPP funding runs against Stripe test mode — the same Payment Auth flow as production, without live money. Before you fund, paste your Stripe test MPP profile id (profile_test_...) in Configuration → Settlement and enable the stripe_mpp rail.

BehaviorSandboxProduction
Stripe charge pathStripe test mode with your profile_test_* profileStripe live mode with your profile_* profile
Tempo deposit confirmationOn-chain on Tempo testnet (Moderato) when session funding is available; otherwise session open may stay session_open_pendingOn-chain on Tempo mainnet after the deposit confirms
MPP profile & Tempo recipientSet in Settlement settings (profile_test_*, optional Tempo recipient); masked in consoleSet in Settlement settings; masked in console
First /fund without credentialMay return dual WWW-Authenticate headers (charge and session) only when Tempo session funding is enabled and a recipient address is setSame gate — session challenge appears only when session mode is enabled
Capability tokenMinted after Stripe test charge or Tempo deposit confirmationMinted after live charge or on-chain deposit confirmation

Dual challenges. When Tempo session funding is enabled and a recipient is configured, the first charge-mode /fund without a credential may return both charge and session WWW-Authenticate values. Kit selects automatically:

  • fundWithMppChargeintent="charge", method="stripe"
  • fundWithMppSessionintent="session", method="tempo"

For the fastest sandbox path without writing MPP wallet code, use paybond.guardrails.bootstrapSandbox(...) to get a pre-funded intent — see One-command guardrails.

Offline Kit tests can use paybond dev loop --offline; end-to-end Stripe test-mode MPP and Tempo testnet checks are opt-in.

Kit integration

One-shot charge (TypeScript)

paybond-session.ts

TS
TypeScript code sampleSwipe to inspect long lines
const created = await paybond.intents.create({
  operation: "saas.api.purchase",
  requestedSpendCents: 5_000,
  currency: "usd",
  settlementRail: "stripe_mpp",
  completionPreset: "api_response_ok",
});

const funded = await paybond.intents.fundWithMppCharge({
  intentId: created.intent_id,
  recognitionProof: await issueAgentRecognitionProofV1({
    purpose: "harbor.intent.fund",
    method: "POST",
    path: `/harbor/intents/${created.intent_id}/fund`,
    body: {},
  }),
  createPaymentCredential: (challenge) => mppWallet.createPaymentCredential(challenge),
  issueRecognitionProof: (envelope) =>
    issueAgentRecognitionProofV1({
      purpose: "harbor.intent.fund",
      method: envelope.method,
      path: envelope.path,
      body: envelope.body,
    }),
});

const capabilityToken = funded.capabilityToken;

Tempo session channel (Python)

paybond_session.py

PY
Python code sampleSwipe to inspect long lines
funded = await paybond.intents.fund_with_mpp_session(
    intent_id=intent_id,
    recognition_proof=fund_recognition_proof,
    create_payment_credential=mpp_wallet.create_payment_credential,
    issue_recognition_proof=issue_fund_recognition_proof,
)
capability_token = funded.capability_token

Prefer these helpers over manual /fund loops — they parse challenges, invoke your credential callback, set paymentAuthorization, poll pending states, and stop on terminal failures (PaybondMppFundingFailedError / PaybondMppFundingPendingError in TypeScript).

Funding response fields

After challenges progress, inspect funding on the fund result:

FieldChargeSessionMeaning
intent, method, challenge_idActive Payment Auth challenge metadata
settlement_asset, settlement_networkDenomination Harbor snapshots (USDC on Tempo for MVP)
deposit_amount_base_unitsUSD cents × 10 000 for Tempo USDC base units
session_protocol, channel_idTempo v2 channel identity
accepted_cumulative_base_units, pending_cumulative_base_unitsVoucher metering state
descriptor_hash, channel_statusChannel integrity and lifecycle

See SDK reference — IntentFundingResult and Harbor MPP handshake.

Session voucher metering at verify

After session funding, the intent stays funded while the agent runs metered tool calls. Harbor does not advance the intent to released on each voucher — terminal payout still requires evidence submission and settlement confirmation.

For stripe_mpp session intents, POST /harbor/verify (Gateway POST /verify) performs capability checks and accepts a Tempo voucher credential when metering spend inside the open channel.

Verify handshake (session intents only)

StepHTTPRequest headersResponse
1. ChallengePOST /verifyAuthorization: Bearer <api_key> and verify JSON body200 with allow: false, code: mpp_voucher_required, WWW-Authenticate: Payment … (intent="session", method="tempo", scope: verify_voucher in opaque metadata); Cache-Control: no-store
2. CredentialPOST /verify (retry)Same body and x-paybond-payment-authorization: Payment <voucher-credential>200 with allow: true when capability and voucher pass; Payment-Receipt on success; Cache-Control: private
Rejected voucherPOST /verifyCredential present but invalid or over budget200 with allow: false, code: mpp_voucher_rejected, fresh WWW-Authenticate; no pending_cumulative_base_units mutation

Charge-mode MPP intents skip the voucher path — capability verification alone is sufficient after one-shot funding.

Budget cap and cumulative vouchers

Paybond enforces the signed intent budget (amount_cents, converted to USDC base units as amount_cents × 10 000) at voucher acceptance time:

  • Each voucher carries a monotonic cumulative spend total for the Tempo channel.
  • Vouchers whose cumulative total exceeds the intent budget are rejected, even when the session deposit ceiling is higher.
  • Only one pending voucher may be in flight per intent. Complete gateway spend finalization (commit or release) before submitting the next voucher credential.
  • Rejected vouchers do not advance accepted_cumulative_base_units or set pending_cumulative_base_units.

Inspect funding.accepted_cumulative_base_units and funding.pending_cumulative_base_units on verify responses and intent detail for metering state. Kit spend guards still enforce per-operation requested_spend_cents against the capability token — vouchers meter Tempo spend under the same intent ceiling.

Challenge binding on fund and verify

MPP challenges include:

ControlBehavior
Challenge TTLExpires at the earlier of intent deadline and a capped window (default 15 minutes). Expired credentials return a fresh challenge with payment_expired.
POST body digestChallenges bind SHA-256 of the exact fund or verify request body (base64url). Retries must send the same body bytes used when the challenge was issued.
Opaque tenant/intent bindingCredentials echo an opaque blob tying the challenge to tenant_id, intent_id, and (for verify) scope: verify_voucher.
Challenge ID consumptionEach challenge_id is consumed atomically before settlement side effects. Replayed credentials fail without state mutation.

Empty-body fund requests use the digest of an empty string. Verify requests use the JCS-canonical JSON body on POST /verify.

Tenant isolation — critical boundaries

MPP funding touches payment credentials and channel state. Treat cross-tenant access as a severity-zero defect.

RuleWhy
Never pass tenant_id from unauthenticated user inputKit derives tenant scope from the API key. Mismatched tenant context is rejected on every write.
Never supply Stripe profile ids, Tempo recipient addresses, or channel_id in create/fund payloadsDestinations are server-owned from settlement config. Client-supplied routing is ignored or rejected.
Bind recognition proofs to the authenticated tenantverifier_context.tenant_id must match your authenticated workspace.
One capability token per intentTokens are minted when that intent reaches funded. Reusing across intents fails verify.
Treat tenant/intent mismatch errors as fatalDo not retry errors about tenant or intent mismatch — investigate immediately.
Do not log credentialsNever log API keys, JWTs, x-paybond-payment-authorization values, capability tokens, or wallet signing material.

Cross-tenant fund or voucher attempts are rejected. Stale or replayed challenge_id and voucher credentials fail without changing intent state. Vouchers that would exceed the intent amount_cents budget are rejected.

Integration checklist

Tenant admin (before first MPP fund)

  • Paid plan activated (production) or sandbox workspace selected
  • Stripe Connect destination linked (MPP routes through your linked Stripe destination)
  • In Configuration → Settlement, paste your MPP profile id (profile_test_... in sandbox, profile_... in production) and confirm readiness shows the profile as configured
  • For session mode: set a Tempo recipient and enable Tempo session funding in Settlement settings
  • stripe_mpp added to allowed rails

Application developer

  • settlementRail: "stripe_mpp" / settlement_rail: "stripe_mpp" with currency: "usd"
  • Chose charge or session helper before first /fund
  • App-owned createPaymentCredential / create_payment_credential wired
  • Fresh recognition proof per /fund attempt
  • Guard tools only after capability_token is present
  • For session mode: wire voucher credentials on POST /verify (via spend guard) and respect single pending voucher semantics
  • Submit tool-completion evidence after guarded work — not Payment Auth receipts or Stripe PaymentIntent ids

Common mistakes

  • Using fundWithMppCharge when you need streaming vouchers — switch to fundWithMppSession at create time.
  • Sending Authorization: Payment through Gateway — use x-paybond-payment-authorization so Bearer auth still works.
  • Treating 402 with application/problem+json as success — only 200 with a capability_token means funded; 202 with session_open_pending means keep polling.
  • Submitting a second voucher while one is pending — wait for spend completion finalize (commit/release) before the next verify credential.
  • Assuming deposit size overrides intent budget — cumulative vouchers are capped at signed amount_cents, not merely the Tempo channel deposit.
  • Submitting funding payloads as completion evidence — the completion rule evaluates vendor tool results, not MPP challenge ids.
  • EUR or multi-currency intents on stripe_mpp — rejected at create until policy expands.

Where to go next