feat(x402): tolerant chat-path gateway, structured payment errors, funnel metrics - #688
Closed
OisinKyne wants to merge 1 commit into
Closed
feat(x402): tolerant chat-path gateway, structured payment errors, funnel metrics#688OisinKyne wants to merge 1 commit into
OisinKyne wants to merge 1 commit into
Conversation
…nnel metrics
- HandleProxy rewrites bare POST /services/<name> (and /chat/completions,
/v1) to /v1/chat/completions for agent/inference offers, so the most
common wrong-path mistake from external buyers succeeds instead of
paying into a 404; the 402 page's agent copy taught exactly that bare
form until this change
- terminal payment failures return structured JSON {error, reason, hint,
retriable}; the facilitator's invalidReason (previously discarded) now
rides the re-issued 402 challenge in error + extensions.paymentFailure,
and signature rejections state the expected EIP-712 domain
- legacy error phrases kept verbatim (flows/lib.sh greps for them)
- new funnel metrics: payment_failure_reasons_total{reason} (bounded
6-value set) and upstream_failed_after_verify_total, so first-try buyer
success is measurable per stage; docs/observability.md updated
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
|
Superseded by #690. The buyer-funnel stack (#686 → #687 → #688) and the x402 v2 Leaving this open for the author to close in favor of #690. |
OisinKyne
added a commit
that referenced
this pull request
Jul 2, 2026
…des #686–689) (#690) * feat(buy-x402): go auto-dispatch front door and buyer UX hardening * feat(catalog): single-source buyer prompts, docs deep links, signing metadata - new internal/buyprompts package is the one authoring point for how-to-buy copy; the 402 page, /api/services.json ('buy' block with callShape + prompts per buyer-software kind), the storefront, and 'obol sell info' all render it, so instructions can no longer drift between surfaces (the storefront was still advertising the removed --no-verify-identity flag) - catalog entries gain openapiPath + docsPath (Scalar deep-link anchor); storefront shows API-docs links for every offer type, skill.md service details link the anchored docs - x-payment-info always emits accepts[] with payTo, CAIP-2 network, atomic amount, and the asset's EIP-712 signing domain, so an OpenAPI-only client can construct a valid X-PAYMENT without a second fetch - @scalar/api-reference bumped 1.34.0 -> 1.62.1; renovate custom manager + postUpgradeTasks now keep it current, with scripts/update-scalar-sri.sh refreshing the SRI hash inside the same renovate PR Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(x402): tolerant chat-path gateway, structured payment errors, funnel metrics - HandleProxy rewrites bare POST /services/<name> (and /chat/completions, /v1) to /v1/chat/completions for agent/inference offers, so the most common wrong-path mistake from external buyers succeeds instead of paying into a 404; the 402 page's agent copy taught exactly that bare form until this change - terminal payment failures return structured JSON {error, reason, hint, retriable}; the facilitator's invalidReason (previously discarded) now rides the re-issued 402 challenge in error + extensions.paymentFailure, and signature rejections state the expected EIP-712 domain - legacy error phrases kept verbatim (flows/lib.sh greps for them) - new funnel metrics: payment_failure_reasons_total{reason} (bounded 6-value set) and upstream_failed_after_verify_total, so first-try buyer success is measurable per stage; docs/observability.md updated Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(x402): accept x402 v2 PAYMENT-SIGNATURE header in verifier The ForwardAuth verifier advertises x402Version 2 in its 402 challenge but only read the payment from the legacy v1 X-PAYMENT header. Spec-compliant x402 v2 buyers (agentcash, poncho, coinbase SDK >= v2) attach the payment under PAYMENT-SIGNATURE, so their valid payment was silently ignored and the caller re-challenged — no verify, no settle, no log. This blocked every third-party v2 client from paying obol endpoints; only the in-tree buyer (which sends X-PAYMENT) worked. - Read the payment from X-PAYMENT (v1) OR PAYMENT-SIGNATURE (v2). - Decode via the canonical x402types.ToPaymentPayload helper instead of a local json.Unmarshal, keeping the envelope in lockstep with the SDK. - Mirror the settlement receipt onto both X-PAYMENT-RESPONSE and PAYMENT-RESPONSE. - Tests for the v2 header accept + settle + dual response header. Claude-Session: https://claude.ai/code/session_01VquWN9UMaSHH7MHGcG8bw1 * fix(x402): meter v2 (PAYMENT-SIGNATURE) payments in the funnel Post-#689 a spec-compliant x402 v2 payment arrives under PAYMENT-SIGNATURE, but the funnel gate hadPayment only checked X-PAYMENT — so every successful v2 payment (exactly the cohort #689 unblocked) incremented none of the success/charge/upstream counters. Gate on both headers. Emergent defect of integrating #688 (X-PAYMENT-gated metrics) with #689 (v2 via PAYMENT-SIGNATURE). --------- Co-authored-by: Oisín Kyne <oisin@obol.tech> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: bussyjd <145845+bussyjd@users.noreply.github.com> Co-authored-by: Oisín Kyne <4981644+OisinKyne@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Two gateway behaviors were silently killing first-try purchases:
POST <endpoint>bare, while the upstream (Hermes) only serves/v1/chat/completions. A buyer following the page verbatim � with a perfectly valid signed payment � got proxied to/and failed. Every other surface (buy.py, skill.md, openapi.json, storefront) taught the correct path; this was the odd one out.Payment verification failed, 503). The facilitator'sinvalidReason/invalidMessage� the only information that turns a failed retry into a successful one � was logged server-side and discarded. Retrying agents learned nothing and burned attempts.What
HandleProxyrewritesPOST /services/<name>(bare),�/chat/completions, and�/v1to/v1/chat/completionsfor agent/inference offers before proxying. Other sub-paths (/v1/embeddings), non-POST methods, and non-chat offer types pass through untouched. This forgives the most common wrong-path mistake from every buying client we don't control (off-the-shelf x402 SDKs, weak models truncating URLs).X-PAYMENT, facilitator unreachable, settlement error) return JSON{error, reason, hint, retriable}. Rejected payments keep the spec-correct re-issued 402 challenge but now carry the facilitator's rejection inerrorand a machine-readableextensions.paymentFailureblock. Signature-flavored rejections state the expected EIP-712 domain � the seller is the only party who knows it. The settle-error path preserves theX-PAYMENT-RESPONSEtx-hash behavior and warns against double-pay retries.Invalid payment header,Payment verification failed,Payment settlement failed) kept verbatim in theerrorfield �flows/lib.shgreps and buy.py hint detection keep working (and get richer input).obol_x402_verifier_payment_failure_reasons_total{reason}(bounded, code-enumerated 6-value set) and_upstream_failed_after_verify_total(payment verified, seller's own upstream bounced the buyer, nothing settled). First-try success is now attributable per stage.docs/observability.mdupdated per its own label conventions.Testing
paymentFailureextension, EIP-712 hint on signature rejection, retriable 503 on facilitator-down./internal/x402/...suite green, including the SSE-streaming and settle-tx-hash regression testsPost-merge
Worth a release-smoke run (
flows/flow-07/08/16/17) against a live cluster � unit tests can't exercise the Traefik hop in front ofHandleProxy.� Generated with Claude Code