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. A froglet node can only mint an SPT in test mode (via Stripe test_helpers). In production the SPT is granted by the buyer’s external agentic platform — a ChatGPT-style ACP agent — not by a froglet node.
  • froglet-node → froglet-node Stripe payment is test-only, because only the test path can mint the buyer’s token.
  • US-only. SPTs are currently restricted to US accounts.
  • Preview API. Requires the preview header Stripe-Version: 2026-04-22.preview.
  • Attested, not cryptographic. Stripe settlement proof is a PaymentIntent reference — an attestation. The cryptographic settlement rail is Lightning (its payment preimage is the cryptographic proof).

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

This keeps the same local prepare/commit/release lifecycle as the other local/self-hosted adapters while using Stripe’s own primitives underneath. It does not currently add Stripe-specific quote/deal settlement terms to the signed kernel flow.

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, not try.froglet.dev:

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.