Stripe
The agentic Shared Payment Token model
Section titled “The agentic Shared Payment Token model”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
PaymentIntentreference — an attestation. The cryptographic settlement rail is Lightning (its payment preimage is the cryptographic proof).
Use a Stripe test-mode secret key by default:
cd froglet && FROGLET_STRIPE_SECRET_KEY=<stripe-test-secret-key> \./scripts/setup-payment.sh stripeThat writes .froglet/payment/stripe.env with:
FROGLET_PAYMENT_BACKEND=stripeFROGLET_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_...Verification
Section titled “Verification”The setup helper probes Stripe by calling:
GET https://api.stripe.com/v1/accountThe 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.
Runtime behavior
Section titled “Runtime behavior”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_tokenparameter - commit captures the payment intent
- release cancels the uncaptured payment intent
POST /v1/webhooks/stripeverifies 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.
Webhook endpoint
Section titled “Webhook endpoint”When FROGLET_STRIPE_WEBHOOK_SECRET is set, Froglet exposes:
POST /v1/webhooks/stripeRegister the public HTTPS URL in Stripe test mode after the node is reachable, for example:
https://paid-staging.froglet.dev/v1/webhooks/stripeListen only to the payment-intent events used by this adapter:
payment_intent.succeededpayment_intent.payment_failedpayment_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.
Paid-staging smoke
Section titled “Paid-staging smoke”The first-party hosted Stripe proof uses a separate operator surface, not
try.froglet.dev:
cd ../froglet-servicesFROGLET_PAID_STAGING_URL=https://paid-staging.froglet.dev \ ./ops/paid_staging_stripe_smoke.shThat 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.
Webhook alerting
Section titled “Webhook alerting”The live paid-staging alerting surface is a Cloudflare Worker probe:
https://paid-staging-stripe-probe.froglet.dev/healthThe 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:
$.statusequalsok - Optional assertions:
$.upstream_statusequals200,$.processedequalstrue,$.duplicateequalsfalse
Manual alert drill:
- Wait until
/healthis green in the external monitor. - Temporarily change the monitor URL to
https://paid-staging-stripe-probe.froglet.dev/fail. - Wait for the DOWN alert.
- Restore the URL to
/health. - 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.