> ## 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.

# 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

This setup works on the **free tier** out of the box — no API keys required.

Beyond the free tier the facilitator authenticates merchants with short-lived
Ed25519 JWTs. Create a merchant account at
[merchant.payai.network](https://merchant.payai.network), then add your keys 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>
