Go SDK for the Machine Payments Protocol (MPP) — inline HTTP 402 payments for APIs and AI agents.
MPP lets API servers charge per-request or per-session using standard HTTP headers — no payment dashboard, no API keys, no billing setup on the client. The protocol extends HTTP 402 with a WWW-Authenticate: Payment challenge/response flow:
Client Server
│ │
│──── GET /api/data ───────────────>│
│ │ (no Authorization header)
│<─── 402 Payment Required ─────────│
│ WWW-Authenticate: Payment │
│ realm="my-api" │
│ method="tempo" │
│ intent="charge" │
│ request="<base64url-json>" │
│ │
│ [client pays / signs credential] │
│ │
│──── GET /api/data ───────────────>│
│ Authorization: Payment │
│ challenge=<challenge> │
│ payload=<credential> │
│ │
│<─── 200 OK ───────────────────────│
│ Payment-Receipt: Payment ... │
mpp-go provides:
- Server middleware for net/http, Gin, Echo, and Fiber
- Client that auto-handles 402 challenges and retries
- Payment methods: Tempo (on-chain EVM), Stripe, Mock (testing)
- Session payments (payment channels / pay-as-you-go)
- SSE streaming with metered voucher flow
- MCP integration for AI agent payments (Model Context Protocol)
go get github.com/deszhou/mpp-goFor Ethereum/Tempo on-chain verification, build with the geth tag:
go build -tags geth ./...package main
import (
"net/http"
netmpp "github.com/deszhou/mpp-go/http/nethttp"
"github.com/deszhou/mpp-go/methods/mock"
"github.com/deszhou/mpp-go/server"
)
func main() {
mppServer := server.New(
server.WithMethod(&mock.ServerMethod{}),
server.WithRealm("my-api"),
)
charge := server.StaticCharge(mock.MethodName, "1000000", "pathUSD", nil) // 1.00 USD
http.Handle("/api/data", netmpp.Middleware(mppServer, charge)(myHandler))
http.ListenAndServe(":8080", nil)
}package main
import (
"net/http"
netmpp "github.com/deszhou/mpp-go/http/nethttp"
"github.com/deszhou/mpp-go/methods/tempo"
"github.com/deszhou/mpp-go/server"
"github.com/deszhou/mpp-go/store"
)
func main() {
verifier := tempo.NewEthVerifier() // requires -tags geth
mppServer := server.New(
server.WithMethod(tempo.NewServerMethod(verifier,
tempo.WithRecipient("0xYourAddress"),
tempo.WithNetworkID(42431),
)),
server.WithStore(store.NewMemoryStore()),
server.WithRealm("my-api"),
server.WithSecretKey("your-secret-key"),
)
charge := server.StaticCharge("tempo", "1000000", "pathUSD", nil) // 1.00 USD
http.Handle("/api/data", netmpp.Middleware(mppServer, charge)(myHandler))
http.ListenAndServe(":8080", nil)
}The client automatically handles 402 challenges and retries with payment:
package main
import (
"context"
"fmt"
mppClient "github.com/deszhou/mpp-go/client"
"github.com/deszhou/mpp-go/methods/mock"
)
func main() {
c := mppClient.NewHTTPClient(
[]mppClient.PaymentProvider{
&mock.ClientProvider{WalletAddress: "0xYourAddress"},
},
)
resp, err := c.Get(context.Background(), "https://api.example.com/data")
fmt.Println(resp.Status, err)
}For Tempo on-chain payments (requires -tags geth):
signer := tempo.NewEthSigner(privateKey)
c := mppClient.NewHTTPClient(
[]mppClient.PaymentProvider{tempo.NewClientProvider(signer)},
)After payment verification, the receipt is available from the request context:
func myHandler(w http.ResponseWriter, r *http.Request) {
receipt := netmpp.ReceiptFromContext(r.Context())
fmt.Fprintf(w, "paid via %s, ref: %s", receipt.Method, receipt.Reference)
}All middleware adapters share the same behavior: no Authorization header → issue 402; invalid credential → reissue 402; valid payment → call next handler with receipt in context.
import ginmpp "github.com/deszhou/mpp-go/http/gin"
r := gin.New()
r.Use(ginmpp.Middleware(mppServer, charge))
r.GET("/api/data", func(c *gin.Context) {
receipt := ginmpp.ReceiptFromContext(c)
c.JSON(200, gin.H{"receipt": receipt})
})import echompp "github.com/deszhou/mpp-go/http/echo"
e := echo.New()
e.Use(echompp.Middleware(mppServer, charge))
e.GET("/api/data", func(c echo.Context) error {
receipt := echompp.ReceiptFromContext(c)
return c.JSON(200, receipt)
})import fibermpp "github.com/deszhou/mpp-go/http/fiber"
app := fiber.New()
app.Use(fibermpp.Middleware(mppServer, charge))
app.Get("/api/data", func(c *fiber.Ctx) error {
receipt := fibermpp.ReceiptFromContext(c)
return c.JSON(receipt)
})Use a ChargeFunc closure for per-request pricing:
charge := func(r *http.Request) (protocol.MethodName, string, string, *server.ChargeOptions, error) {
amount := computePrice(r) // your pricing logic
return "tempo", amount, "pathUSD", nil, nil
}Session payments use payment channels for streaming or multi-request billing. The client opens a channel with an initial deposit, then sends signed vouchers for each unit consumed.
// Server: register a session method
mppServer := server.New(
server.WithSessionMethod(tempoSession.NewServerMethod(channelStore)),
)
// Server: verify each request
result, _, err := mppServer.VerifySession(ctx, r.Header.Get("Authorization"))
if result.ManagementResponse != nil {
// lifecycle event (Open / TopUp / Close) — return to client
json.NewEncoder(w).Encode(result.ManagementResponse)
return
}
// proceed: payment channel has sufficient balanceFor streaming APIs billed by token or chunk:
import "github.com/deszhou/mpp-go/sse"
// Server sends SSE events; client pays per voucher
srv := sse.NewServer(mppServer, channelStore)
srv.ServeHTTP(w, r) // handles voucher negotiation automaticallyFor Model Context Protocol tool servers, mpp-go provides JSON-RPC helpers:
import "github.com/deszhou/mpp-go/mcp"
// Extract payment challenge from a -32042 JSON-RPC error
challenge, err := mcp.ParsePaymentRequiredError(jsonRPCErr)
// Attach credential to tool call params
params = mcp.AttachCredential(params, credential)| Package | Description |
|---|---|
github.com/deszhou/mpp-go/server |
Core server: Mpp, challenge/verify, charge & session |
github.com/deszhou/mpp-go/client |
HTTP client with auto-retry on 402 |
github.com/deszhou/mpp-go/protocol |
Wire types: challenge, credential, receipt, headers |
github.com/deszhou/mpp-go/methods/tempo |
Tempo on-chain payment method (charge + session) |
github.com/deszhou/mpp-go/methods/stripe |
Stripe Payment Intent method |
github.com/deszhou/mpp-go/methods/mock |
In-memory mock for testing |
github.com/deszhou/mpp-go/http/nethttp |
net/http middleware |
github.com/deszhou/mpp-go/http/gin |
Gin middleware |
github.com/deszhou/mpp-go/http/echo |
Echo middleware |
github.com/deszhou/mpp-go/http/fiber |
Fiber middleware |
github.com/deszhou/mpp-go/sse |
SSE event types and metered streaming |
github.com/deszhou/mpp-go/mcp |
MCP (Model Context Protocol) payment helpers |
github.com/deszhou/mpp-go/digest |
HTTP body digest (sha-256=<base64>) |
github.com/deszhou/mpp-go/mperr |
RFC 9457 Problem Details error types |
github.com/deszhou/mpp-go/store |
Challenge store (memory + channel state) |
github.com/deszhou/mpp-go/expires |
RFC 3339 expiry timestamp helpers |
| Tag | Effect |
|---|---|
geth |
Enables tempo.NewEthVerifier() and tempo.NewEthSigner() via go-ethereum |
Without geth, on-chain Ethereum operations are unavailable but all other packages compile normally. This keeps the default dependency footprint small.
mpp-go implements the Machine Payments Protocol specification:
HTTP 402+WWW-Authenticate: Payment realm="…" method="…" intent="…" request="…"- HMAC-SHA256 challenge IDs (32-byte, base64url) — wire-compatible with mpp-rs
- JCS (RFC 8785) canonical JSON encoding for payment requests
- RFC 9457 Problem Details error responses
- RFC 3339 timestamps for challenge expiry
See the examples/ directory:
| Example | Description |
|---|---|
examples/server |
net/http server with mock payment |
examples/client |
Client that auto-pays 402 challenges |
examples/tempo |
Tempo flow with mock EVM signer/verifier — run with go run ./examples/tempo (no build tags). Real RPC via NewEthSigner / NewEthVerifier needs -tags geth. |
Run the mock examples locally:
go run ./examples/server &
go run ./examples/client
go run ./examples/tempoPull requests are welcome. For significant changes, open an issue first to discuss the approach.
go test ./...
go test -tags geth ./...
go vet ./...