> ## Documentation Index
> Fetch the complete documentation index at: https://docs.payai.network/llms.txt
> Use this file to discover all available pages before exploring further.

> Add x402 payment protection to a Go Gin server and settle payments through the PayAI facilitator.

# Gin

## Getting started with Gin (Go)

Start accepting x402 payments in your Gin server in 2 minutes.

<Note>You can find the full code for this example [here](https://github.com/x402-foundation/x402/tree/main/examples/go/servers/gin).</Note>

### Step 1: Create a Go module and install dependencies

```bash theme={null}
go mod init myserver
go get github.com/x402-foundation/x402/go github.com/gin-gonic/gin github.com/joho/godotenv
go mod tidy
```

### Step 2: Set your environment variables

Your `.env` file should look like this:

```
EVM_PAYEE_ADDRESS=0x...   # EVM wallet address to receive payments
SVM_PAYEE_ADDRESS=...     # Solana wallet address to receive payments
FACILITATOR_URL=https://facilitator.payai.network
```

### Step 3: Preview the server code

This is the `main.go` the example uses. It loads your env, applies the x402 payment middleware, and defines protected and health routes.

```go theme={null}
package main

import (
	"fmt"
	"net/http"
	"os"
	"time"

	x402 "github.com/x402-foundation/x402/go"
	x402http "github.com/x402-foundation/x402/go/http"
	ginmw "github.com/x402-foundation/x402/go/http/gin"
	evm "github.com/x402-foundation/x402/go/mechanisms/evm/exact/server"
	svm "github.com/x402-foundation/x402/go/mechanisms/svm/exact/server"
	ginfw "github.com/gin-gonic/gin"
	"github.com/joho/godotenv"
)

const (
	DefaultPort = "4021"
)

func main() {
	godotenv.Load()

	evmAddress := os.Getenv("EVM_PAYEE_ADDRESS")
	if evmAddress == "" {
		fmt.Println("❌ EVM_PAYEE_ADDRESS environment variable is required")
		os.Exit(1)
	}

	svmAddress := os.Getenv("SVM_PAYEE_ADDRESS")
	if svmAddress == "" {
		fmt.Println("❌ SVM_PAYEE_ADDRESS environment variable is required")
		os.Exit(1)
	}

	facilitatorURL := os.Getenv("FACILITATOR_URL")
	if facilitatorURL == "" {
		fmt.Println("❌ FACILITATOR_URL environment variable is required")
		fmt.Println("   Example: https://x402.org/facilitator")
		os.Exit(1)
	}

	// Network configuration - Base Sepolia testnet
	evmNetwork := x402.Network("eip155:84532")
	svmNetwork := x402.Network("solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1")

	fmt.Printf("🚀 Starting Gin x402 server...\n")
	fmt.Printf("   EVM Payee address: %s\n", evmAddress)
	fmt.Printf("   SVM Payee address: %s\n", svmAddress)
	fmt.Printf("   EVM Network: %s\n", evmNetwork)
	fmt.Printf("   SVM Network: %s\n", svmNetwork)
	fmt.Printf("   Facilitator: %s\n", facilitatorURL)

	// Create Gin router
	r := ginfw.Default()

	// Create HTTP facilitator client
	facilitatorClient := x402http.NewHTTPFacilitatorClient(&x402http.FacilitatorConfig{
		URL: facilitatorURL,
	})

	/**
	 * Configure x402 payment middleware
	 *
	 * This middleware protects specific routes with payment requirements.
	 * When a client accesses a protected route without payment, they receive
	 * a 402 Payment Required response with payment details.
	 */
	routes := x402http.RoutesConfig{
		"GET /weather": {
			Accepts: x402http.PaymentOptions{
				{
					Scheme:  "exact",
					Price:   "$0.001",
					Network: "eip155:84532",
					PayTo:   evmAddress,
				},
				{
					Scheme:  "exact",
					Price:   "$0.001",
					Network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
					PayTo:   svmAddress,
				},
			},
			Description: "Get weather data for a city",
			MimeType:    "application/json",
		},
	}

	// Apply x402 payment middleware
	r.Use(ginmw.X402Payment(ginmw.Config{
		Routes:      routes,
		Facilitator: facilitatorClient,
		Schemes: []ginmw.SchemeConfig{
			ginmw.SchemeConfig{Network: "eip155:84532", Server: evm.NewExactEvmScheme()},
			ginmw.SchemeConfig{Network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", Server: svm.NewExactSvmScheme()},
		},
		Timeout:    30 * time.Second,
	}))

	/**
	 * Protected endpoint - requires $0.001 USDC payment
	 *
	 * Clients must provide a valid x402 payment to access this endpoint.
	 * The payment is verified and settled before the endpoint handler runs.
	 */
	r.GET("/weather", func(c *ginfw.Context) {
		city := c.DefaultQuery("city", "San Francisco")

		weatherData := map[string]map[string]interface{}{
			"San Francisco": {"weather": "foggy", "temperature": 60},
			"New York":      {"weather": "cloudy", "temperature": 55},
			"London":        {"weather": "rainy", "temperature": 50},
			"Tokyo":         {"weather": "clear", "temperature": 65},
		}

		data, exists := weatherData[city]
		if !exists {
			data = map[string]interface{}{"weather": "sunny", "temperature": 70}
		}

		c.JSON(http.StatusOK, ginfw.H{
			"city":        city,
			"weather":     data["weather"],
			"temperature": data["temperature"],
			"timestamp":   time.Now().Format(time.RFC3339),
		})
	})

	/**
	 * Health check endpoint - no payment required
	 *
	 * This endpoint is not protected by x402 middleware.
	 */
	r.GET("/health", func(c *ginfw.Context) {
		c.JSON(http.StatusOK, ginfw.H{
			"status":  "ok",
			"version": "2.0.0",
		})
	})

	fmt.Printf("   Server listening on http://localhost:%s\n\n", DefaultPort)

	if err := r.Run(":" + DefaultPort); err != nil {
		fmt.Printf("Error starting server: %v\n", err)
		os.Exit(1)
	}
}
```

### Step 4: Run the server

```bash theme={null}
go run .
```

<Check>
  Your server is now accepting 402 payments!
</Check>

### Step 5: Test the server

You can test payments against your server locally by following the [fetch example](/x402/clients/typescript/fetch) or the [axios example](/x402/clients/typescript/axios), or by using the Go client examples in the [x402 repository](https://github.com/x402-foundation/x402/tree/main/examples/go/clients).

## Going to production

The examples above use testnets. For Mainnet, update the route's network and any explicit scheme registrations together, use the matching asset and funded buyer, and verify the paid path. The [Solana Mainnet example](/x402/solana-mainnet-express) provides a complete tested configuration; adding credentials alone does not switch networks.

This setup can serve ordinary exact mainnet payments in production within the
[free allowance](/x402/facilitators/pricing) — no API key or portal signup is
required. Production readiness still includes normal controls such as secret
management, monitoring, and failure handling.

Beyond the allowance the facilitator authenticates merchants with short-lived
Ed25519 JWTs. An autonomous agent can [buy credits and receive an API key over
x402](/x402/facilitators/agent-api-keys); PayAI creates a wallet-owned agent
account programmatically. You can also use the optional [merchant
dashboard](https://merchant.payai.network). Add the resulting credentials to
your `.env`:

```env theme={null}
PAYAI_API_KEY_ID=your-key-id
PAYAI_API_KEY_SECRET=payai_sk_your-api-key-secret
```

There is no PayAI module for Go — the x402 SDK takes an `AuthProvider`, so
signing the token is all you need. Put this in `payaiauth/provider.go`:

```go theme={null}
// Package payaiauth signs short-lived EdDSA JWTs for the PayAI facilitator.
package payaiauth

import (
	"context"
	"crypto/ed25519"
	"crypto/rand"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"strings"
	"sync"
	"time"

	x402http "github.com/x402-foundation/x402/go/http"
)

// Provider implements x402http.AuthProvider.
type Provider struct {
	keyID string
	key   ed25519.PrivateKey
	ttl   time.Duration

	mu      sync.Mutex
	token   string
	renewAt time.Time
}

// New builds a Provider from a PayAI API key ID and secret. The secret may
// carry the payai_sk_ prefix shown in the merchant dashboard.
func New(apiKeyID, apiKeySecret string) (*Provider, error) {
	secret := strings.TrimPrefix(strings.TrimSpace(apiKeySecret), "payai_sk_")
	der, err := base64.StdEncoding.DecodeString(secret)
	if err != nil {
		if der, err = base64.URLEncoding.DecodeString(secret); err != nil {
			return nil, fmt.Errorf("decode API key secret: %w", err)
		}
	}
	parsed, err := x509.ParsePKCS8PrivateKey(der)
	if err != nil {
		return nil, fmt.Errorf("parse PKCS#8 key: %w", err)
	}
	key, ok := parsed.(ed25519.PrivateKey)
	if !ok {
		return nil, fmt.Errorf("API key secret is not an Ed25519 key")
	}
	return &Provider{keyID: apiKeyID, key: key, ttl: 120 * time.Second}, nil
}

func b64(raw []byte) string {
	return base64.RawURLEncoding.EncodeToString(raw)
}

func uuidV4() (string, error) {
	var b [16]byte
	if _, err := rand.Read(b[:]); err != nil {
		return "", err
	}
	b[6] = (b[6] & 0x0f) | 0x40
	b[8] = (b[8] & 0x3f) | 0x80
	return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16]), nil
}

func (p *Provider) jwt() (string, error) {
	p.mu.Lock()
	defer p.mu.Unlock()
	if p.token != "" && time.Now().Before(p.renewAt) {
		return p.token, nil
	}
	now := time.Now()
	jti, err := uuidV4()
	if err != nil {
		return "", err
	}
	header, _ := json.Marshal(map[string]string{"alg": "EdDSA", "typ": "JWT", "kid": p.keyID})
	payload, _ := json.Marshal(map[string]any{
		"sub": p.keyID, "iss": "payai-merchant",
		"iat": now.Unix(), "exp": now.Add(p.ttl).Unix(), "jti": jti,
	})
	msg := b64(header) + "." + b64(payload)
	p.token = msg + "." + b64(ed25519.Sign(p.key, []byte(msg)))
	// refresh early so an in-flight request never carries a token that expires
	// mid-lifetime
	p.renewAt = now.Add(p.ttl - 30*time.Second)
	return p.token, nil
}

// GetAuthHeaders satisfies x402http.AuthProvider.
func (p *Provider) GetAuthHeaders(ctx context.Context) (x402http.AuthHeaders, error) {
	t, err := p.jwt()
	if err != nil {
		return x402http.AuthHeaders{}, err
	}
	h := map[string]string{"Authorization": "Bearer " + t}
	return x402http.AuthHeaders{Verify: h, Settle: h, Supported: h, Bazaar: h}, nil
}
```

Then pass it to the facilitator client:

```go theme={null}
authProvider, err := payaiauth.New(
	os.Getenv("PAYAI_API_KEY_ID"),
	os.Getenv("PAYAI_API_KEY_SECRET"),
)
if err != nil {
	fmt.Printf("❌ PayAI auth: %v\n", err)
	os.Exit(1)
}

facilitatorClient := x402http.NewHTTPFacilitatorClient(&x402http.FacilitatorConfig{
	URL:          facilitatorURL,
	AuthProvider: authProvider,
})
```

The provider caches each token and re-signs 30 seconds before expiry, so a
long-running server signs roughly once every two minutes rather than on every
request. See [Facilitator Pricing](/x402/facilitators/pricing) for tier details
and [Facilitator Authentication](/x402/facilitators/authentication) for the
protocol reference.

## x402 reference

For a deeper dive into message shapes, headers, verification and settlement responses, see the [x402 Reference](/x402/reference).

## Need help?

<Card title="Join our Community" icon="discord" href="https://discord.gg/eWJRwMpebQ">
  Have questions or want to connect with other developers? Join our Discord server.
</Card>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.