X402-Payment is an early-stage Go project in the AI payments / x402 ecosystem. It currently has 1 GitHub stars and 0 forks.
Phase 1 skeleton for a three-party x402 payment platform:
buyer/ keeps the Python demo client and approval boundarycmd/platformd hosts the Go platform entrypointinternal/control serves /platform/*internal/gateway serves /relay/*internal/x402 is the only package that should talk to the official Go x402 SDKThis repository now contains:
control, gateway, registry, provider, ledger, audit, and x402402 -> approve -> sign -> retry flowpayment_id + request_digest pollingread_only, idempotent_write, and effectful_write with the approved execution-mode matrixMYSQL_DSN is unsetread / write scopesunknown settlement inspection and manual recoveryPOST /platform/providers/healthz, /readyz, /readyz/control, and /readyz/relay system routesWhat is still intentionally stubbed:
POST /platform/providersGET /platform/providers/:providerIDPOST /platform/providers/:providerID/api-key/rotateGET /platform/providers/:providerID/api-keysPOST /platform/providers/:providerID/api-keysDELETE /platform/providers/:providerID/api-keys/:keyID?reason=...POST /platform/providers/:providerID/manifestsPOST /platform/providers/:providerID/manifests/:interfaceID/publishPOST /platform/providers/:providerID/manifests/:interfaceID/unpublishGET /platform/providers/:providerID/routesGET /platform/providers/:providerID/ledgerGET /platform/providers/:providerID/ledger?settlement_status=unknownGET /platform/providers/:providerID/ledger?payment_id=pay_123&interface_id=weather.v1.current&limit=50GET /platform/providers/:providerID/ledger?requested_after=2026-03-15T00:00:00Z&requested_before=2026-03-16T00:00:00ZGET /platform/providers/:providerID/payments/:paymentIDGET /platform/providers/:providerID/auditGET /platform/providers/:providerID/audit?event_type=manifest_published&limit=50GET /platform/providers/:providerID/audit?created_after=2026-03-15T00:00:00Z&created_before=2026-03-16T00:00:00ZGET /platform/admin/providers/:providerIDGET /platform/admin/providers?status=suspendedGET /platform/admin/providers?status=active&limit=50GET /platform/admin/providers?created_after=2026-03-15T00:00:00ZPUT /platform/admin/providers/:providerID/statusGET /readyz/controlGET /readyz/relayGET /relay/payments/:paymentID/result?request_digest=...pending, the endpoint returns 202unknown, the endpoint returns 503payment_id is queried with a different request_digest, the endpoint returns 409read_only -> ephemeral, TTL-controlledidempotent_write -> ephemeral, longer TTL-controlledeffectful_write -> durable, no automatic expirygo test ./...
go run ./cmd/platformd
go run ./cmd/fakefacilitator
go run ./cmd/demo-upstream
go run ./cmd/reconcile list-unknown
go run ./cmd/reconcile list-expired-pending
go run ./cmd/reconcile expire-pending
go run ./cmd/resultgc -before 2026-03-15T00:00:00Z -limit 500
python -m unittest discover -s buyer/tests -p 'test_*.py'
python -m buyer GET http://localhost:8080/relay/providers/weather/v1/current
make test
go run ./cmd/fakefacilitator
go run ./cmd/demo-upstream
/relay/* can reach localhost:DEV_ALLOW_PRIVATE_UPSTREAMS=true \
X402_FACILITATOR_URL=http://localhost:8081 \
go run ./cmd/platformd
python scripts/demo_setup.py
To try a real paid write flow instead of the weather read path:
python scripts/demo_setup.py --manifest examples/checkout_manifest.json
python -m buyer POST http://localhost:8080/relay/providers/checkout/v1/create --body '{"sku":"book_1","quantity":1}'
To try the first provider-managed effectful flow:
python scripts/demo_setup.py --manifest examples/charge_manifest.json
python -m buyer POST http://localhost:8080/relay/providers/checkout/v1/charge --body '{"order_id":"ord_demo_1","amount":"1000"}'
To simulate "provider completed work but the first response was lost", add:
python -m buyer POST http://localhost:8080/relay/providers/checkout/v1/charge --header X-Demo-Lose-Response=once --body '{"order_id":"ord_demo_1","amount":"1000"}'
For effectful_write + provider_managed, the manifest must declare extensions.provider_managed.status_target, the target must contain {payment_id}, and status_method must be GET. The provider status endpoint should return one of these envelopes:
{"state":"pending","operation_id":"op_pay_123"}
{
"state": "succeeded",
"operation_id": "op_pay_123",
"result_http_status": 200,
"result_headers": {"Content-Type": "application/json"},
"result_body": {"ok": true, "charge_id": "chg_123"}
}
{"state":"failed","operation_id":"op_pay_123","error_detail":"provider rejected the charge"}
Malformed envelopes are rejected during recovery. In particular, operation_id is required for every state, failed requires error_detail, and succeeded requires both a 2xx result_http_status and result_body.
buyer_command, or run the buyer directly:python -m buyer GET http://localhost:8080/relay/providers/weather/v1/current
If the signed request times out after payment submission, rerun the buyer with the same command only if it reuses the same signed payment, or query:
curl -H "X-X402-Recovery-Key: <sha256(PAYMENT-SIGNATURE)>" \
"http://localhost:8080/relay/payments/<payment_id>/result?request_digest=<digest>"
The buyer now persists payment operations to ~/.x402-payment/buyer_operations.json by default. Successful paid requests print both operation_id and payment_id, and an interrupted agent can resume with:
python -m buyer --recover-operation <operation_id>
Use --state-file /path/to/ops.json if you want a different local state file. The buyer computes and sends the recovery key automatically during operation recovery.
For effectful_write + provider_managed, duplicate signed relay requests no longer re-drive the provider after settlement. Recovery flows through GET /relay/payments/<payment_id>/result and the provider status_target contract instead.
In other words, provider-managed recovery is a status lookup contract for an already-submitted operation, not a platform-side re-execution path.
The provider-managed recovery contract is now stricter: status_target must contain {payment_id}, status_method must be GET, and every valid status envelope must include operation_id. Recovered or failed settlements also persist provider_operation_id, so provider-facing control queries can surface the upstream operation handle during incident review.
Result retention is configured with:
RESULT_TTL_READ_ONLY_SECONDS=604800
RESULT_TTL_IDEMPOTENT_WRITE_SECONDS=2592000
PROVIDER_MANAGED_RECOVERY_INTERVAL_SECONDS=30
PROVIDER_MANAGED_RECOVERY_BATCH_SIZE=25
PENDING_RESERVATION_CLEANUP_INTERVAL_SECONDS=60
PENDING_RESERVATION_CLEANUP_BATCH_SIZE=100
When MySQL is enabled, platformd also runs a background provider-managed recovery pass. It periodically scans settled + fulfillment_status=unknown records, polls the provider status_target, and closes them to succeeded, failed, or keeps them pending without waiting for a buyer poll. Set PROVIDER_MANAGED_RECOVERY_INTERVAL_SECONDS=0 to disable the loop.
platformd also runs a pending-reservation cleanup pass. It periodically marks stale settlement_status=pending reservations older than the lease window as not_started + failed with error_detail=pending_reservation_expired, so abandoned reservations do not remain in the backlog forever. Set PENDING_RESERVATION_CLEANUP_INTERVAL_SECONDS=0 to disable that loop.
If PLATFORM_ADMIN_TOKEN is configured, operators can inspect the most recent recovery pass via:
curl -H "Authorization: Bearer $PLATFORM_ADMIN_TOKEN" \
http://localhost:8080/platform/admin/recovery/provider-managed
The response includes whether the loop is enabled, interval/batch settings, last-run timestamps, last-run scan counts, and cumulative success/failure/pending/error totals.
Operators can also fetch a compact platform snapshot via:
curl -H "Authorization: Bearer $PLATFORM_ADMIN_TOKEN" \
http://localhost:8080/platform/admin/metrics
The admin metrics response includes provider counts by lifecycle status, settlement backlog counts (pending, unknown, and settled + fulfillment_status=unknown), and the current provider-managed recovery snapshot when that loop is enabled.
If you want a scrape-friendly text format instead of JSON:
curl -H "Authorization: Bearer $PLATFORM_ADMIN_TOKEN" \
http://localhost:8080/platform/admin/metrics/prometheus
This returns plain-text gauges for provider counts, settlement backlog, and provider-managed recovery loop state.
You can also use the shortcut targets in Makefile:
make run-fakefacilitator
make run-demo-upstream
make run-platform
make run-reconcile
make run-resultgc
make demo-setup
read_only + proxy_after_verifyidempotent_write + proxy_after_verifyeffectful_write + provider_managedInnoDB, utf8mb4, and READ COMMITTEDVARCHAR(78)DATETIME(6)TEXTwrite: can submit/publish manifests, manage pay-to whitelist, and manage provider API keysread: can query provider profile, manifests, routes, audit, ledger, and single-payment statePOST /platform/providers returns the initial write keyPROVIDER_REGISTRATION_TOKEN to require X-Provider-Registration-Token on POST /platform/providersPOST /platform/providers/:providerID/api-key/rotate revokes the current caller key and issues a fresh write key with the same label/scope setlimit with a default of 100 and a maximum of 500/platform/admin/providers, /platform/providers/:providerID/audit, and /platform/providers/:providerID/ledger support cursor pagination via cursor and return next_cursormeta object with limit and next_cursor, while keeping the legacy top-level fields for compatibility/platform/admin/metrics returns provider and settlement backlog metrics for operator use/platform/admin/metrics/prometheus returns the same metrics as Prometheus-style text gaugesPLATFORM_ADMIN_TOKEN to enable /platform/admin/*/readyz/control checks process + DB readiness for control-plane use/readyz/relay checks process + DB + x402/facilitator readiness for relay trafficPUT /platform/admin/providers/:providerID/status accepts active, suspended, or disabledsuspended or disabled removes all currently published routes immediatelyactive, manifest submit/publish is rejected; after reactivation the provider must republish explicitlyMYSQL_DSN='user:pass@tcp(127.0.0.1:3306)/x402_payment?parseTime=true' \
go run ./cmd/reconcile list-unknown
MYSQL_DSN='user:pass@tcp(127.0.0.1:3306)/x402_payment?parseTime=true' \
go run ./cmd/reconcile list-unknown -provider-id prov_123
MYSQL_DSN='user:pass@tcp(127.0.0.1:3306)/x402_payment?parseTime=true' \
go run ./cmd/reconcile list-expired-pending -before 2026-03-15T00:00:00Z -limit 100
MYSQL_DSN='user:pass@tcp(127.0.0.1:3306)/x402_payment?parseTime=true' \
go run ./cmd/reconcile expire-pending -before 2026-03-15T00:00:00Z -limit 100
settled after external reconciliation:MYSQL_DSN='user:pass@tcp(127.0.0.1:3306)/x402_payment?parseTime=true' \
go run ./cmd/reconcile resolve -payment-id pay_123 -status settled -tx-hash 0xabc
failed when external settlement is confirmed absent:MYSQL_DSN='user:pass@tcp(127.0.0.1:3306)/x402_payment?parseTime=true' \
go run ./cmd/reconcile resolve -payment-id pay_123 -status failed -error-detail "manual investigation confirmed failure"
Your AI trading terminal assistant for US stocks, commodities, forex, and crypto.
Golang SDK for A2A Protocol
Publishes localhost services to the agentic web through self-hostable, trustless relays with x402 payments.
Building blocks for Agentic payments (x402, MPP, AP2) for TypeScript, Rust, Go, Python, Ruby, PHP, Lua, Kotlin and Swift.
Building blocks for Agentic payments (x402, MPP, AP2) for TypeScript, Rust, Go, Python, Ruby, PHP, Lua, Kotlin and Swift.
Go implementation of the x402 payment protocol