v0.14.0 - Discoverable by buyers you didn't build for
Point an external buyer tool at your storefront and it can enumerate everything you sell, then pay for it. No integration, no prior knowledge of your endpoints.
Until now a buyer had to already know one of your offers to pay it. v0.14.0 publishes a single stack-level x402 discovery document at /.well-known/x402 on your shared storefront, listing every paid route across every ready offer — and the compatibility work that lets AgentCash, Bankr, Poncho and generic x402 SDK clients actually find and pay it.
Around that: an agent chat widget on every agent offer, safe operator branding previews, an opt-in paid unlock gate, and a round of payment-path hardening that stops buyers being charged for streams they never received.
Warning
This software is early alpha, you could lose what you put in. Please use caution when it comes to non-testnet assets.
Install / Upgrade
# Install
bash <(curl -s https://stack.obol.org)
# Run the stack
obol stack init && obol stack up
# See what your stack advertises to external buyers
curl -s https://<your-storefront>/.well-known/x402 | jqUpgrading from v0.13.x is in place — obol upgrade, then obol stack up.
Release Highlights
One discovery document for the whole stack
Your shared storefront serves an aggregate x402 document at /.well-known/x402, with one resources[] entry per paid route across every operationally-ready offer, rooted at each offer's effective public path, in a stable order.
Offers that are deleted, drained, or not yet ready are omitted, so the document only ever advertises routes a buyer can pay right now. Per-offer documents on dedicated origins are unchanged — this is an addition, not a replacement.
External buyer tools can pay your endpoints
The storefront's buyer guidance gained a dedicated external tab with per-tool prompts for AgentCash, Bankr, Poncho and a generic x402 SDK client. The payment path picked up the compatibility fixes those tools need:
- Facilitator failures are classified. A transport failure is now distinguished from a facilitator that answered and rejected the payment. Previously both surfaced as "unreachable", sending buyer agents into loops of identical retries that could never succeed.
- Legacy network aliases are advertised alongside CAIP-2 identifiers, so buyers pinned to the older naming still match.
- The facilitator URL is echoed in the 402 body when safe to disclose, so a buyer can construct a payment without out-of-band configuration.
- Payload tolerance: a
resourcesent as a plain string, and ECDSA signatures usingv = 0/1instead of27/28, are normalized rather than rejected.
Don't debit a buyer who never got the stream
On streaming (text/event-stream) responses, settlement is deferred until the response completes, and skipped when the client disconnected, when writing failed, or when no body ever reached them. Previously a buyer whose connection dropped mid-stream — a common agent timeout — could still be charged for output they never received.
Talk to the agent you're selling
Every type: agent offer gets a /chat page and a live chat widget embedded on its landing page, themed per offer. Buyers can try the agent before paying, and the widget speaks the same x402 payment path as any other client.
Safer operator branding previews
Storefront branding drafts render through the real public storefront renderer instead of a duplicated preview UI, so what you see is what buyers get. Previews are session-only and pinned to storefront-preview.obol.stack — they never publish to your public tunnel. Branding is applied with server-side apply, so large inline logos no longer hit kubectl's 256 KiB annotation limit.
Opt-in paid unlock gate
authCaptureUnlock turns one gate: auth offer from free wallet sign-in into pay once to sign in. The offer's free SIWX endpoints are suppressed, and the only way to mint a session is a single inline payment on the first request; everything after that is session-authenticated, not paid again.
That one payment is split on-chain via AuthCaptureEscrow — a buyer-signed, bounded percentage (minFeeBps–maxFeeBps) to feeRecipient, the remainder to payTo.
It is opt-in and disabled by default — no fee is taken unless you configure it. To be precise about what it is not: it is not a cut of every payment your stack receives, and it cannot route a share to an upstream provider. The split has exactly two legs and fires only on the unlock.
Important
Base mainnet requires the in-cluster facilitator. The hosted facilitator at https://x402.gcp.obol.tech advertises auth-capture on Base Sepolia only (eip155:84532) — check its /supported. For mainnet, point facilitatorURL at the facilitator sidecar (http://localhost:8090), which carries v2-eip155-auth-capture for eip155:8453.
Note
Two current limits: only one unlock offer per stack (offerPrefix is global), and a ServiceOffer cannot declare scheme: auth-capture — its CRD enum permits exact only. Per-offer configuration is the follow-up. Also note that in v0.14.0 this is configured by patching the x402-pricing ConfigMap, which obol stack up reverts; templating it into the chart is tracked separately.
The running CLI version, in the UI
obol stack up and tunnel sync write obolVersion into obol-stack-config and inject OBOL_STACK_VERSION into the frontend pod, so the web UI footer shows the CLI release actually running.
Fixes since v0.13.0
obol stack upfailed outright on the k3s backend. Kubernetes has rejected loopback addresses inEndpointssince v1.33, and that is what host resolution handed theollamaEndpoints. The guard now lives in the shared resolver, so it also covers Docker runtimes that maphost.docker.internalto loopback. (#808)- The branding editor's preview never loaded.
storefront-preview.obol.stackwas missing from the managed/etc/hostsblock, because the agent-resume path rewrote the block last and dropped it. Now emitted unconditionally. (#808) - Root-priced
type: httpoffers advertised an unpayable method. The offer root serves your landing page onGETand onlyPOSTreaches the payment gate, so advertisingGETpointed buyers at a page that could never return 402. The advertised method is now derived from the route's shape. (#806) - A slow upstream could collapse an offer's advertised routes. A failed upstream-OpenAPI probe was cached and pinned the offer to a bare root entry until its ServiceOffer was edited; separately, a controller restart re-rendered not-yet-probed offers from the fallback. Failed probes are no longer cached, and already-published documents are held while a probe is unsettled. (#810, #813)
- CodeQL
go/log-injectionin the ForwardAuth middleware: the buyer's payment payload and the facilitator's reject reason reached the operator log unescaped, so a crafted CRLF value could forge log lines. Untrusted strings are stripped of CR/LF at the producing functions.
Breaking changes / Migration notes
Important
type: http offers and advertised methods. A paid route that does not declare methods is advertised as GET when it is a sub-path route, and as POST when it is root-priced (/, /*, or an offer with no route table). Inference, agent and fine-tuning offers are POST throughout. If a sub-path paid route takes a JSON body, declare methods: [POST] on it explicitly — otherwise buyers will be told to call it the wrong way.
- Everything else upgrades in place. The aggregate
/.well-known/x402needs no configuration; it appears once the controller is upgraded. - The auth-capture unlock gate is opt-in and off by default. Upgrading does not enable a fee.
Known issues
Note
Streaming settlement receipts are HTTP trailers. On SSE responses the X-PAYMENT-RESPONSE receipt is delivered as a trailer rather than a header — a consequence of deferring settlement until the stream completes. Many proxies and clients do not surface trailers, so streaming buyers may not see a receipt even though settlement succeeded; verify on-chain if you need confirmation. Non-streaming responses are unaffected.
Note
obol stack down on the k3s backend does not clean host netfilter state. Orphaned CNI hostport rules can survive teardown and intercept host-local :80/:443. If loopback stops working after a teardown, check sudo iptables -t nat -S for CNI-HOSTPORT-DNAT jump rules in OUTPUT/PREROUTING and remove them. The k3d backend is unaffected.
Validation
This release was validated before tagging, not only in CI:
- Full release smoke on two hosts — 18 of 19 flows passing on the primary host, including both live-network flows.
- 11 on-chain receipts on Base Sepolia (USDC and OBOL Permit2 settlements, a Permit2 approval, ERC-8004 registrations), each verified independently against the public RPC.
- A live nine-offer stack upgraded and re-probed end to end: every paid path returns a correct 402, aggregate discovery serves, and per-offer documents advertise methods that reach the payment gate.
- Both k3s fixes confirmed on real hosts rather than in unit tests alone.
Full Changelog: v0.13.0...v0.14.0
