Accept s402 Payments in Go
s402 is language-agnostic. This example uses Go's net/http standard library — no external dependencies. The same pattern works with Gin, Echo, Fiber, or any Go HTTP framework.
The 30-Second Version
An s402 server does three things:
- Return 402 with a
payment-requiredheader (base64 JSON) - Decode the
x-paymentheader from the client's retry - Verify the Sui transaction via RPC
~60 lines of Go. No SDK required.
Full Example (net/http)
package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
const (
payTo = "0xYOUR_ADDRESS_HERE"
price = "1000000" // 0.001 SUI in MIST
suiRPC = "https://fullnode.testnet.sui.io:443"
)
func encodeB64JSON(v any) string {
b, _ := json.Marshal(v)
return base64.StdEncoding.EncodeToString(b)
}
func decodeB64JSON(s string) (map[string]any, error) {
b, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return nil, err
}
var m map[string]any
return m, json.Unmarshal(b, &m)
}
func write402(w http.ResponseWriter) {
requirements := map[string]any{
"s402Version": "1",
"accepts": []string{"exact"},
"network": "sui:testnet",
"asset": "0x2::sui::SUI",
"amount": price,
"payTo": payTo,
"expiresAt": time.Now().Add(5 * time.Minute).UnixMilli(),
}
w.Header().Set("payment-required", encodeB64JSON(requirements))
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(402)
json.NewEncoder(w).Encode(map[string]string{"error": "Payment Required"})
}
func verifySuiTx(txDigest string) bool {
body, _ := json.Marshal(map[string]any{
"jsonrpc": "2.0",
"id": 1,
"method": "sui_getTransactionBlock",
"params": []any{txDigest, map[string]bool{"showEffects": true}},
})
resp, err := http.Post(suiRPC, "application/json", bytes.NewReader(body))
if err != nil {
return false
}
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
var result struct {
Result struct {
Effects struct {
Status struct {
Status string `json:"status"`
} `json:"status"`
} `json:"effects"`
} `json:"result"`
}
json.Unmarshal(data, &result)
return result.Result.Effects.Status.Status == "success"
}
func premiumData(w http.ResponseWriter, r *http.Request) {
paymentHeader := r.Header.Get("x-payment")
if paymentHeader == "" {
write402(w)
return
}
// Decode payment payload
payload, err := decodeB64JSON(paymentHeader)
if err != nil {
http.Error(w, `{"error":"Invalid payment header"}`, 400)
return
}
// Extract tx digest from payload
inner, _ := payload["payload"].(map[string]any)
txDigest, _ := inner["txDigest"].(string)
if txDigest == "" || !verifySuiTx(txDigest) {
http.Error(w, `{"error":"Payment verification failed"}`, 402)
return
}
// Payment verified — serve premium content
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{
"data": "This is premium data you paid for!",
"txDigest": txDigest,
})
}
func main() {
http.HandleFunc("/api/premium-data", premiumData)
fmt.Println("s402 server listening on :3402")
http.ListenAndServe(":3402", nil)
}Run It
go run server.goNo dependencies to install. Just go run.
Test It
# Step 1: Get the 402 response
curl -i http://localhost:3402/api/premium-data
# → 402 with payment-required header
# Step 2: Decode the requirements (pipe through base64 -d)
curl -s http://localhost:3402/api/premium-data -D - -o /dev/null 2>&1 \
| grep payment-required \
| cut -d' ' -f2 \
| base64 -d \
| python3 -m json.toolThe client-side payment flow (building the Sui transaction, signing, retrying) is handled by any s402 client with a Sui scheme implementation.
Production Hardening
The example above is minimal. For production, add:
- Amount verification: Inspect
balanceChangesin the RPC response to confirm the correct amount was sent to your address - Replay protection: Store processed
txDigestvalues in a set/database - Expiry enforcement: Reject payments where the requirements have expired
- HTTPS: Required — payment headers are base64, not encrypted
- Concurrent safety: Use
sync.Mapor similar for the replay protection set
Conformance Testing
s402 ships 161 machine-readable JSON test vectors in spec/vectors/. Use them to verify your Go implementation matches the spec — no TypeScript required:
npm pack s402 && tar xzf s402-*.tgz
ls package/test/conformance/vectors/Load the vectors, run your encode/decode functions against them, and compare results. See the Conformance Vectors guide for a complete Go example.
Key Takeaway
Go's standard library is enough to accept s402 payments. No SDK, no dependencies, no framework required. The entire server-side integration is: decode base64, parse JSON, POST to Sui RPC.