mpp-go

June 4, 2026 · View on GitHub



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"
	"net/http"

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

func main() {
	method, _ := charge.MethodFromConfig(charge.Config{
		RPCURL: "https://rpc.moderato.tempo.xyz",
		Recipient: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
	})

	payment := server.New(method, "api.example.com", "replace-me")

	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)
}

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",
	})

	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

ExampleDescription
basicSeparate-process demo with a long-running server and standalone client, mirroring the mpp-rs sample layout
chi/serverChi router example using the existing net/http middleware helpers
gin/serverGin example using the dedicated adapter package and context helpers
echo/serverEcho example using the dedicated adapter package and context helpers
fiber/serverFiber example using the dedicated adapter package and context helpers
charge-basicGeneric Tempo charge flow using the high-level MPP client and server helpers, available in both one-command and separate-process layouts
charge-hashPush-mode charge flow with a hash credential, available in both one-command and separate-process layouts
charge-fee-payerSponsored Tempo charge flow where the server co-signs as a fee payer, available in both one-command and separate-process layouts

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.

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.