Skip to content

Stripe

Froglet’s Stripe rail uses Stripe’s Agentic Commerce “Shared Payment Token” (SPT) model. Read the buyer/seller split carefully — it is not generic “pay with your credit card.”

  • Froglet is the seller / merchant. It accepts and charges an SPT, creating a manual-capture PaymentIntent (prepare creates it, commit captures, release cancels the uncaptured intent).
  • The buyer side is external in production. Froglet’s built-in SPT minting helper uses Stripe test_helpers and requires an explicit sandbox opt-in and a test secret key. Production buyers obtain an SPT from an authorized external agentic-commerce platform.
  • Node-to-node Stripe calls can carry an externally issued SPT. The requester runtime accepts payment.kind: "stripe_mpp" and payment.token and forwards the token to the provider. Only the built-in minting helper is sandbox-only; accepting an external token does not establish live-money qualification.
  • US-only. SPTs are currently restricted to US accounts.
  • Preview API. Requires the preview header Stripe-Version: 2026-04-22.preview.
  • Stripe settlement is attested. A signed Froglet Receipt binds the provider’s settlement assertion to a PaymentIntent reference. Its signature does not independently establish that Stripe captured the payment. Lightning payment preimages provide a separate cryptographic settlement witness.

Use a Stripe test-mode secret key by default:

Terminal window
cd froglet && FROGLET_STRIPE_SECRET_KEY=<stripe-test-secret-key> \
./scripts/setup-payment.sh stripe

That writes .froglet/payment/stripe.env with:

Terminal window
FROGLET_PAYMENT_BACKEND=stripe
FROGLET_STRIPE_SECRET_KEY=<stripe-test-secret-key>
FROGLET_STRIPE_API_VERSION=2026-04-22.preview
# Optional after you register a Stripe webhook endpoint:
FROGLET_STRIPE_WEBHOOK_SECRET=whsec_...

The setup helper probes Stripe by calling:

GET https://api.stripe.com/v1/account

The probe uses the configured secret key and Stripe API version header. Test keys must return livemode=false. Live keys are accepted only when the operator sets FROGLET_STRIPE_LIVE_CONFIRM=fresh for a fresh live proof; do not claim live fiat support until that proof and refund path are recorded.

The current public adapter reuses the configured numeric service price directly for the PaymentIntent amount. It does not perform FX conversion from sats into Stripe fiat units.

For Froglet’s local /v1/node/* settlement lifecycle, Stripe maps to:

  • descriptor exposes stripe_mpp
  • prepare validates the shared payment token and creates a manual-capture payment intent
  • the PaymentIntent uses Stripe’s shared_payment_granted_token parameter
  • commit captures the payment intent
  • release cancels the uncaptured payment intent
  • POST /v1/webhooks/stripe verifies Stripe webhook signatures and stores each event id once for retry-safe operator evidence

These legacy synchronous helpers use the same local prepare/commit/release lifecycle as the other local/self-hosted adapters.

The signed Quote → Deal → Receipt flow also supports the Kernel’s stripe_mpp.v1 settlement method. The Quote specifies the base fee; the provider reserves a manual-capture PaymentIntent when admitting the paid Deal. A terminal Receipt records the PaymentIntent reference and captured or canceled settlement state. Stripe uses no InvoiceBundle; its success-fee entry is a zero-valued canceled placeholder. See the Kernel settlement methods.

Native MCP run_compute defaults to free-only and has no caller-supplied SPT argument. Paid Lightning computation uses the configured wallet and spending policy. Advanced Stripe callers supply their externally issued token through the requester runtime API; the optional automatic minting helper remains sandbox-only. Local free computations and mock paid lifecycle tests do not qualify live-money Stripe execution, refunds or restart recovery.

When FROGLET_STRIPE_WEBHOOK_SECRET is set, Froglet exposes:

POST /v1/webhooks/stripe

Register the public HTTPS URL in Stripe test mode after the node is reachable, for example:

https://paid-staging.froglet.dev/v1/webhooks/stripe

Listen only to the payment-intent events used by this adapter:

  • payment_intent.succeeded
  • payment_intent.payment_failed
  • payment_intent.canceled

The route requires Stripe’s raw request body and Stripe-Signature header. Invalid signatures return 400; duplicate event ids return 200 with duplicate: true so Stripe retries do not double-process the event.

The first-party hosted Stripe proof uses a separate operator surface:

Terminal window
cd ../froglet-services
FROGLET_PAID_STAGING_URL=https://paid-staging.froglet.dev \
./ops/paid_staging_stripe_smoke.sh

That smoke expects a test-mode sk_test_... key and whsec_... webhook secret in Keychain. It verifies health, Stripe capability advertisement, webhook signature acceptance, forged-signature rejection, duplicate-event idempotency, fresh test SPT creation, and one stripe_mpp token-settled /v1/node/events/query call. Paid-staging uses a Stripe-safe test price because Stripe rejects sub-minimum PaymentIntent amounts.

The 2026-04-30 paid-staging checks also captured a Stripe Dashboard-origin payment_intent.succeeded delivery returning HTTP 200 from the public webhook endpoint, and a Stripe CLI resend that returned to pending_webhooks=0.

The live paid-staging alerting surface is a Cloudflare Worker probe:

https://paid-staging-stripe-probe.froglet.dev/health

The probe signs a fresh synthetic Stripe-style payment_intent.succeeded event and posts it through the real public webhook endpoint. It returns HTTP 200 only when Froglet accepts a fresh event and responds with processed:true, duplicate:false, and upstream_status:200.

Use this probe for UptimeRobot, BetterStack, or an equivalent API monitor. Do not monitor POST /v1/webhooks/stripe directly from a static uptime check: Stripe webhook delivery requires a fresh timestamped Stripe-Signature HMAC header.

Monitor assertions:

  • URL: https://paid-staging-stripe-probe.froglet.dev/health
  • Method: GET
  • Expected HTTP status: 200
  • JSON assertion: $.status equals ok
  • Optional assertions: $.upstream_status equals 200, $.processed equals true, $.duplicate equals false

Manual alert drill:

  1. Wait until /health is green in the external monitor.
  2. Temporarily change the monitor URL to https://paid-staging-stripe-probe.froglet.dev/fail.
  3. Wait for the DOWN alert.
  4. Restore the URL to /health.
  5. Wait for the recovery/clear alert.

As of 2026-04-30, the probe itself is live and verified. The external monitor alert-fire/clear drill still requires the operator to create the monitor and attach an alert destination.

Restart recovery is now proven for the VM-backed paid-staging shape, not for the old Lightsail Container Service shape. A 2026-04-30 Lightsail Container Service redeploy replay sent evt_froglet_restart_1777543048 before and after deployment version 3; both deliveries returned processed:true and duplicate:false, proving that container-service-local SQLite state was not durable enough. The hostname was then moved to a Lightsail VM with /opt/froglet/data mounted into the Froglet container. The repeatable restart smoke replayed evt_froglet_restart_1777551288 after recreating the Froglet app container and returned duplicate:true. Live-money Stripe remains outside this proof.