Help us shape the future of Go! Take the 2026 Go Developer Survey

mpp-go

module
v0.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 2, 2026 License: MIT, Apache-2.0, MIT

README



Machine Payments Protocol



mpp-go

Go SDK for the Machine Payments Protocol

Website Docs Go Reference License

MPP lets any client — agents, apps, or humans — pay for any service in the same HTTP request. It standardizes HTTP 402 with an open IETF specification, so servers can charge and clients can pay without API keys, billing accounts, or checkout flows.

Documentation

You can get started today by reading the Go SDK docs, the module-level Go doc reference, exploring the protocol overview, or jumping straight to the quickstart.

Package docs:

Install

go get github.com/tempoxyz/mpp-go

Quick Start

Server
package main

import (
	"encoding/json"
	"log"
	"net/http"

	"github.com/tempoxyz/mpp-go/pkg/server"
	"github.com/tempoxyz/mpp-go/pkg/tempo"
	charge "github.com/tempoxyz/mpp-go/pkg/tempo/server"
)

func main() {
	method, _ := charge.New(charge.Config{
		// Development only; use a shared persistent Store in production.
		Store:     tempo.NewMemoryStore(),
		RPCURL:    "https://rpc.moderato.tempo.xyz",
		Recipient: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
	})

	// Secret must be at least 32 bytes (generate with: openssl rand -base64 32).
	payment, err := server.New(method, "api.example.com", "replace-me-with-a-32-byte-min-secret")
	if err != nil {
		log.Fatal(err)
	}

	handler := server.ChargeMiddleware(payment, server.ChargeParams{
		Amount:      "0.50",
		Description: "Paid content",
	})(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		_ = json.NewEncoder(w).Encode(map[string]any{
			"data":  "paid content",
			"payer": server.CredentialFromContext(r.Context()).Source,
		})
	}))

	_ = http.ListenAndServe(":8080", handler)
}

If the endpoint already uses Authorization (API keys, Bearer tokens), create the server with server.WithRequiresAuth(true). Challenges then advertise header="Payment-Authorization", and clients send the Payment credential in that header instead of Authorization.

payment, err := server.New(method, "api.example.com", secret, server.WithRequiresAuth(true))
if err != nil {
	log.Fatal(err)
}

handler := server.ChargeMiddleware(payment, server.ChargeParams{
	Amount:      "0.50",
	Description: "Paid content",
})(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	_ = json.NewEncoder(w).Encode(map[string]any{"data": "paid content"})
}))
Explicit replay storage

Local verifier construction requires an explicit Store, so an unconfigured server cannot issue payable challenges. All replicas must share persistent replay keys, with atomic PutIfAbsent, no eviction, and fail-closed behavior on storage errors. tempo.NewMemoryStore() is an explicit single-process development choice; it loses replay protection on restart. Existing applications relying on the implicit memory store must configure storage. Relay verification delegates replay protection to the relay.

Client
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"os"

	"github.com/tempoxyz/mpp-go/pkg/client"
	"github.com/tempoxyz/mpp-go/pkg/mpp"
	charge "github.com/tempoxyz/mpp-go/pkg/tempo/client"
)

func main() {
	method, _ := charge.New(charge.Config{
		PrivateKey: os.Getenv("MPP_PRIVATE_KEY"),
		RPCURL:     "https://rpc.moderato.tempo.xyz",
		// Pay at most 0.50 per request, in any of the 6-decimal Tempo stablecoins.
		PaymentPolicy: charge.MaxAmount("", "0.50", 6),
	})

	c := client.New([]client.Method{method})
	response, err := c.Get(context.Background(), "https://api.example.com/paid")
	if err != nil {
		panic(err)
	}
	defer response.Body.Close()

	receipt, _ := mpp.ParseReceipt(response.Header.Get("Payment-Receipt"))

	var body struct {
		Data  string `json:"data"`
		Payer string `json:"payer"`
	}
	_ = json.NewDecoder(response.Body).Decode(&body)

	fmt.Printf("paid request for %q from %s with receipt %s\n", body.Data, body.Payer, receipt.Reference)
}

Examples

Example Description
basic Separate-process demo with a long-running server and standalone client, mirroring the mpp-rs sample layout
chi/server Chi router example using the existing net/http middleware helpers
gin/server Gin example using the dedicated adapter package and context helpers
echo/server Echo example using the dedicated adapter package and context helpers
fiber/server Fiber example using the dedicated adapter package and context helpers
charge-basic Generic Tempo charge flow using the high-level MPP client and server helpers, available in both one-command and separate-process layouts
charge-hash Push-mode charge flow with a hash credential, available in both one-command and separate-process layouts
charge-fee-payer Sponsored Tempo charge flow where the server co-signs as a fee payer, available in both one-command and separate-process layouts
charge-relay Moderato charge flow that delegates credential validation and broadcast to Tempo API's MPP relay

Idempotent POST Charges

For paid POST routes or agent tool calls, bind each logical operation to a stable ExternalID. The value is included in the MPP charge request and echoed back in the Payment-Receipt, so servers can distinguish a safe retry from a different paid operation. ExternalID is receipt metadata; it does not by itself persist or reject duplicate operations. Your application must keep its own idempotency record or response cache keyed by the client idempotency value before running the paid side effect.

If your API already accepts an Idempotency-Key header, pass that value through as ChargeParams.ExternalID when constructing the charge:

externalID := r.Header.Get("Idempotency-Key")
if externalID == "" {
	http.Error(w, "missing Idempotency-Key", http.StatusBadRequest)
	return
}

result, err := payment.Charge(r.Context(), server.ChargeParams{
	Authorization: r.Header.Get("Authorization"),
	Amount:        "0.50",
	Description:   "Create report",
	ExternalID:    externalID,
})

Use the same ExternalID for retries of the same operation. Do not generate a new value for each retry, and avoid reusing one value across different paid operations. On retry, check the stored idempotency result first and return the cached response or receipt instead of executing the POST operation again.

Split Credential Lifecycle

Server intents can register separate advisory validation and settlement hooks:

intent, err := server.NewIntent("charge", server.IntentHooks{
	Validate:  validateCredential,
	Broadcast: broadcastCredential,
})

The constructed intent satisfies the public server.BroadcastingIntent interface as well as the backwards-compatible server.Intent interface. Custom intents can implement server.ValidatingIntent or server.BroadcastingIntent directly. Servers can run the phases independently:

validation, err := payment.ValidateCredential(ctx, credential)
receipt, err := payment.BroadcastCredential(ctx, credential)

ValidateCredential requires a server.ValidatingIntent and must not consume replay state, sign fee-payer transactions, or broadcast. BroadcastCredential re-validates before settlement. VerifyCredential remains an alias for the mutating broadcast path, and legacy intents that only implement Verify continue to work for paid routes and broadcasts.

The built-in Tempo charge intent implements the split lifecycle locally. Tempo relay methods implement the same lifecycle by delegating validation to POST /v1/mpp/validate and finalization to POST /v1/mpp/broadcast.

Web Frameworks

The most common Go HTTP stacks today are net/http, Gin, Echo, Chi, and Fiber. MPP already integrates directly with net/http, which also covers Chi because Chi handlers and middleware use the standard http.Handler interfaces.

For the most common non-stdlib frameworks, mpp-go ships first-class adapters:

Chi needs no special adapter:

router.With(server.ChargeMiddleware(payment, server.ChargeParams{
	Amount:      "0.50",
	Description: "Paid content",
})).Get("/paid", func(w http.ResponseWriter, r *http.Request) {
	credential := server.CredentialFromContext(r.Context())
	receipt := server.ReceiptFromContext(r.Context())
	_ = json.NewEncoder(w).Encode(map[string]any{
		"payer":   credential.Source,
		"receipt": receipt.Reference,
	})
})

Gin route middleware:

router.GET("/paid", ginadapter.ChargeMiddleware(payment, server.ChargeParams{
	Amount:      "0.50",
	Description: "Paid content",
}), func(c *gin.Context) {
	credential := ginadapter.Credential(c)
	receipt := ginadapter.Receipt(c)
	c.JSON(http.StatusOK, gin.H{
		"payer":   credential.Source,
		"receipt": receipt.Reference,
	})
})

Echo route middleware:

e.GET("/paid", func(c echo.Context) error {
	credential := echoadapter.Credential(c)
	receipt := echoadapter.Receipt(c)
	return c.JSON(http.StatusOK, map[string]any{
		"payer":   credential.Source,
		"receipt": receipt.Reference,
	})
}, echoadapter.ChargeMiddleware(payment, server.ChargeParams{
	Amount:      "0.50",
	Description: "Paid content",
}))

Fiber route middleware:

app.Get("/paid", fiberadapter.ChargeMiddleware(payment, server.ChargeParams{
	Amount:      "0.50",
	Description: "Paid content",
}), func(c *fiber.Ctx) error {
	credential := fiberadapter.Credential(c)
	receipt := fiberadapter.Receipt(c)
	return c.Status(http.StatusOK).JSON(fiber.Map{
		"payer":   credential.Source,
		"receipt": receipt.Reference,
	})
})

Protocol

Built on the "Payment" HTTP Authentication Scheme, an open specification proposed to the IETF. See mpp.dev/protocol for the full protocol overview, or the IETF specification for the wire format.

Contributing

git clone https://github.com/tempoxyz/mpp-go
cd mpp-go
go test ./...

Security

See SECURITY.md for reporting vulnerabilities.

License

Licensed under either of Apache License, Version 2.0 or MIT License at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Payment request safety

Hash-mode clients validate the credential header size before broadcasting. For custom clients, serialize with mpp.FormatCredential(credential) and handle its error.

With server.WithChallengeNonce(true), server-issued challenges have random opaque _mpp_nonce metadata. Do not set that reserved key. Bind authenticated application context with ChargeParams{Meta: map[string]string{"account": authenticatedAccountID}} when a payment must authorize a specific caller. Derive this ID from trusted session authentication, never directly from request input. Nonces prevent challenge collisions; they do not make leaked complete credentials safe. Low-level challenge constructors remain deterministic, so custom issuance must supply its own unique, authenticated context. Existing challenges remain valid until their original expiry.

Nonce issuance is opt-in for upgrade safety. First deploy this release everywhere with nonce issuance off (the default). Verification already accepts nonces, while issuance stays compatible with old replicas. After all verifiers are upgraded, create the server with server.WithChallengeNonce(true) (or set VerifyParams.EnableChallengeNonce when calling VerifyOrChallenge). The shared-challenge collision risk remains until issuance is enabled. Old replicas reject nonced credentials, potentially after a hash payment has settled; do not enable issuance while old verifiers remain. Version-sticky retries are an alternative rollout strategy.

Directories

Path Synopsis
examples
basic/client command
basic/server command
charge-basic command
charge-hash command
charge-mcp command
charge-relay command
chi/server command
echo/server command
fiber/server command
gin/server command
internal
localnet
Package localnet waits until a Tempo node is ready to accept transactions.
Package localnet waits until a Tempo node is ready to accept transactions.
serverevents
Package serverevents lets this module's transports, such as MCP, report a payment event to a server.Mpp's Events hooks without exporting that API from package server.
Package serverevents lets this module's transports, such as MCP, report a payment event to a server.Mpp's Events hooks without exporting that API from package server.
pkg
client
Package client provides an HTTP client with automatic 402 Payment Required handling.
Package client provides an HTTP client with automatic 402 Payment Required handling.
internal/mpptest
Package mpptest builds and formats MPP Challenges and Credentials for tests, panicking where the mpp functions return an error.
Package mpptest builds and formats MPP Challenges and Credentials for tests, panicking where the mpp functions return an error.
internal/tempoproof
Package tempoproof holds the rule shared by the HTTP and MCP clients and the Tempo charge client for zero-amount Tempo proofs: a proof authenticates the payer at the realm the challenge names, so a client signs one only for a realm the server that sent the challenge owns.
Package tempoproof holds the rule shared by the HTTP and MCP clients and the Tempo charge client for zero-amount Tempo proofs: a proof authenticates the payer at the realm the challenge names, so a client signs one only for a realm the server that sent the challenge owns.
mcp/client
Package client pays for MCP tool calls made with the official MCP Go SDK (github.com/modelcontextprotocol/go-sdk), matching mppx's McpClient.wrap.
Package client pays for MCP tool calls made with the official MCP Go SDK (github.com/modelcontextprotocol/go-sdk), matching mppx's McpClient.wrap.
mcp/internal/payment
Package payment holds the MCP payment flow shared by the MCP SDK and raw JSON-RPC server integrations: reading the Credential from _meta, running the charge, and building the payment error.
Package payment holds the MCP payment flow shared by the MCP SDK and raw JSON-RPC server integrations: reading the Credential from _meta, running the charge, and building the payment error.
mcp/jsonrpc
Package jsonrpc charges for MCP tool calls handled as raw JSON-RPC messages, without an MCP SDK, matching mppx's raw MCP server transport (Transport.mcp()):
Package jsonrpc charges for MCP tool calls handled as raw JSON-RPC messages, without an MCP SDK, matching mppx's raw MCP server transport (Transport.mcp()):
mcp/server
Package server charges for MCP tools served with the official MCP Go SDK (github.com/modelcontextprotocol/go-sdk), matching the mppx/mcp/server transport (Transport.mcpSdk):
Package server charges for MCP tools served with the official MCP Go SDK (github.com/modelcontextprotocol/go-sdk), matching the mppx/mcp/server transport (Transport.mcpSdk):
mpp
Package mpp implements the Machine Payments Protocol (MPP) types and header serialization for Go.
Package mpp implements the Machine Payments Protocol (MPP) types and header serialization for Go.
server
Package server provides the generic challenge-or-verify flow for Payment authentication on the server side.
Package server provides the generic challenge-or-verify flow for Payment authentication on the server side.
server/echo
Package echoadapter provides Echo middleware for MPP-protected routes.
Package echoadapter provides Echo middleware for MPP-protected routes.
server/fiber
Package fiberadapter provides Fiber middleware for MPP-protected routes.
Package fiberadapter provides Fiber middleware for MPP-protected routes.
server/gin
Package ginadapter provides Gin middleware for MPP-protected routes.
Package ginadapter provides Gin middleware for MPP-protected routes.
tempo
Package tempo contains the Tempo-specific charge method built on top of the generic pkg/client and pkg/server HTTP 402 flow, including request helpers, attribution utilities, and replay stores for single-process and Redis-backed deployments.
Package tempo contains the Tempo-specific charge method built on top of the generic pkg/client and pkg/server HTTP 402 flow, including request helpers, attribution utilities, and replay stores for single-process and Redis-backed deployments.
tempo/client
Package chargeclient contains the Tempo charge client method for creating Payment Credentials that satisfy Tempo HTTP 402 Challenges.
Package chargeclient contains the Tempo charge client method for creating Payment Credentials that satisfy Tempo HTTP 402 Challenges.
tempo/internal/rpccache
Package rpccache reuses the Tempo JSON-RPC clients that the charge client and server build from URLs, so each is built once rather than per request.
Package rpccache reuses the Tempo JSON-RPC clients that the charge client and server build from URLs, so each is built once rather than per request.
tempo/server
Package chargeserver contains the Tempo charge server method and verifier.
Package chargeserver contains the Tempo charge server method and verifier.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL