Skip to content

Add x402 payment completion telemetry event - #364

Merged
davedumto merged 1 commit into
Vellar-Wallet:devfrom
Y33t-dev:contrib/issue-296-x402-payment-telemetry
Aug 31, 2026
Merged

Add x402 payment completion telemetry event#364
davedumto merged 1 commit into
Vellar-Wallet:devfrom
Y33t-dev:contrib/issue-296-x402-payment-telemetry

Conversation

@Y33t-dev

Copy link
Copy Markdown

closes #296

Summary

x402 payment completion is not tracked as a telemetry event, so a host that wants to know what its agents are spending has to instrument every call site by hand.

This adds contrib/examples/issue-296-x402-payment-telemetry: an x402.payment.completed event carrying the resource id and amount the issue asks for, plus the asset, network, settlement hash, payer, status and duration that make it reconcilable against chain rather than merely countable.

withPaymentTelemetry() wraps the public wallet.x402.fetch, so it works today with no SDK change:

wallet.x402.fetch = withPaymentTelemetry(wallet.x402.fetch, {
  sink: (event) => analytics.track(event.type, toJSON(event)),
  onError: (err) => console.warn("x402 telemetry sink failed", err),
});

Omit sink and the original function is returned untouched, so telemetry is opt-in with no per-call cost when off.

Design rules

Telemetry must never break a payment. The event fires only after money has already moved on-chain. A sink that throws, rejects, hangs, or whose onError itself throws still returns the caller's X402Response. An async sink is deliberately not awaited, so a slow analytics call adds no latency to a settled payment; its rejection is caught internally rather than surfacing as an unhandled rejection. There is no configuration that lets telemetry fail a settled payment.

It must not leak secrets. No request headers or bodies are recorded. PAYMENT-SIGNATURE is a signed authorization, and the SDK already treats leaking it as a security bug (packages/mcp-x402-payer/src/output.ts). Resource URLs are stripped of query and fragment by default, since API keys live in query strings; resourceIdMode allows origin or opt-in full. A URL that fails to parse still has its query cut off lexically.

Amounts stay exact. Base units are bigint and a stroop-precision value can exceed Number.MAX_SAFE_INTEGER. JSON.stringify throws on a bigint, so toJSON() renders amount as a decimal string that round-trips through BigInt(...).

Only settled payments emit. A 402-free response is a cache hit, not a payment; counting it would inflate every metric built on this event. A failed payment emits nothing and rethrows untouched.

Tests

27 tests covering the event shape, URL sanitization, bigint precision and JSON round-tripping, and each way a sink can fail without disturbing the payment.

npx vitest run contrib/examples/issue-296-x402-payment-telemetry

Notes for the maintainer

Two requirements could not be met as literally written, because contributor PRs may only touch contrib/:

  • README observability section. README.md is outside contrib/, so the reference documentation lives in the example's README instead, written so the tables can be lifted into that section verbatim.
  • Emitting from inside the SDK. The example README sketches the in-SDK version: the completion point is where readSettlement() returns in src/x402-client.ts, and an optional telemetry?: X402TelemetryOptions on X402ClientDeps (threaded through X402FacadeDeps.config, alongside budgetAttributes) would emit the same event for every caller. Happy to open that as a follow-up if you widen the scope.

The 18 test failures on dev (in contrib/examples/ and src/session.test.ts, which also fails tsc) are pre-existing and untouched by this branch.

Issue Vellar-Wallet#296: an x402 payment completing is not tracked as a telemetry event,
so a host that wants to know what its agents are spending has to instrument
every call site by hand.

Adds contrib/examples/issue-296-x402-payment-telemetry: an
x402.payment.completed event carrying the resource id and amount the issue
asks for, plus the asset, network, settlement hash, payer, status and
duration that make it reconcilable against chain. withPaymentTelemetry()
wraps the public wallet.x402.fetch, so it works without SDK changes.

Three properties drove the design. Telemetry must never break a payment:
the event fires only after money has already moved, so a sink that throws,
rejects, hangs, or whose onError itself throws still returns the caller's
X402Response, and an async sink is not awaited so it adds no latency. It
must not leak secrets: no headers or bodies are recorded, and resource URLs
are stripped of query and fragment by default, since API keys live there.
Amounts stay exact: base units are bigint, and toJSON renders them as
decimal strings because JSON.stringify throws on a bigint and Number would
silently round a stroop-precision value.

Only settled payments emit. A 402-free response is a cache hit, not a
payment, and counting it would inflate every metric built on this event.

27 tests cover the event shape, URL sanitization, bigint precision, and
each way a sink can fail without disturbing the payment.
@drips-wave

drips-wave Bot commented Aug 30, 2026

Copy link
Copy Markdown

@Y33t-dev Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@vercel

vercel Bot commented Aug 30, 2026

Copy link
Copy Markdown

@Y33t-dev is attempting to deploy a commit to the david's projects Team on Vercel.

A member of the Team first needs to authorize it.

@davedumto
davedumto merged commit cee9d2c into Vellar-Wallet:dev Aug 31, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants