A minimal, complete reference implementation showcasing GameShift as a payment gateway (similar to Stripe Checkout) for off-chain purchases.
This Next.js application demonstrates how to integrate GameShift's hosted checkout to accept both card and cryptocurrency payments for virtual items like in-game currency, power-ups, and other digital goods.
This demo application provides:
- Storefront page with products dynamically loaded from GameShift SKUs
- Dual payment flow supporting both card and cryptocurrency payments
- GameShift hosted checkout integration with payment method selection
- Webhook handling to process payment completion events
- Purchase history page showing completed transactions
- TypeScript with full type safety
- Tailwind CSS for clean, responsive UI
- Next.js 14+ (App Router)
- TypeScript (strict mode)
- Tailwind CSS (styling)
- GameShift API (payment processing)
off-chain-purchases/
├── app/
│ ├── api/
│ │ └── gameshift/
│ │ ├── checkout/route.ts # Create payment endpoint (card or crypto)
│ │ ├── payments/[id]/route.ts # Payment status lookup
│ │ └── webhook/route.ts # Webhook handler
│ ├── orders/
│ │ └── page.tsx # My Purchases page
│ ├── layout.tsx # Root layout with navigation
│ ├── page.tsx # Storefront (main page)
│ └── globals.css # Tailwind CSS imports
├── components/
│ └── ProductCard.tsx # Product display with payment method selector
├── lib/
│ ├── gameshiftClient.ts # GameShift API wrapper
│ ├── skuMapping.ts # SKU fetching and product conversion
│ ├── products.ts # Product management (SKU-based)
│ ├── demoUser.ts # Hardcoded demo user
│ ├── demoOrderStore.ts # In-memory order tracking
│ └── types.ts # TypeScript type definitions
├── .env.local.example # Environment variable template
├── package.json # Dependencies and scripts
└── README.md # This file
npm installCopy the example environment file:
cp .env.local.example .env.localEdit .env.local and add your GameShift credentials:
# GameShift API Configuration
GAMESHIFT_API_BASE=https://api.gameshift.dev
GAMESHIFT_API_KEY=your_api_key_here
GAMESHIFT_ENVIRONMENT=Development
GAMESHIFT_WEBHOOK_SECRET=your_webhook_secret_here
NEXT_PUBLIC_APP_BASE_URL=http://localhost:3000-
API Key:
- Log in to GameShift Dashboard
- Navigate to Settings → API Keys
- Copy your API key
-
Webhook Secret:
- In GameShift Dashboard, go to Settings → Webhooks
- Create a new webhook endpoint (use
http://localhost:3000/api/gameshift/webhookfor local testing or your production URL) - Copy the webhook secret
Important: Products are dynamically loaded from GameShift SKUs. You must create at least one SKU to see products in the storefront.
-
Log in to GameShift Dashboard
-
Navigate to Storefront → SKUs
-
Click Create New SKU
-
Fill in the details:
- Name: Product name (e.g., "Gold Pack 500")
- Description: Product description
- Price: Set the price (e.g., 4.99)
- Currency: Choose USD, USDC, or SOL
- Quantity: Set to -1 for unlimited
-
Click Create
-
Repeat for as many products as you want
npm run devOpen http://localhost:3000 in your browser.
Your products will automatically appear in the storefront!
This app supports two payment flows depending on the payment method selected:
User selects "Card" payment method
↓
Next.js app calls POST /nx/payments
↓
GameShift returns checkoutUrl
↓
User redirected to GameShift card checkout
↓
User completes card payment
↓
GameShift sends webhook
↓
Order marked as paid
User selects "Crypto" payment method
↓
Next.js app calls GET /internal/storefront/skus/{skuId}/purchase
↓
GameShift returns consentUrl (storefront checkout)
↓
User redirected to GameShift crypto checkout
↓
User completes crypto payment (USDC, SOL, etc.)
↓
GameShift sends webhook
↓
Order marked as paid
When a user clicks "Purchase" on a product:
-
Frontend calls
POST /api/gameshift/checkoutwith the product ID and payment method{ "productId": "gold-pack-small", "paymentMethod": "card" // or "crypto" }
-
API Route (
app/api/gameshift/checkout/route.ts) handles two flows:For Card Payments:
- Calls
POST /nx/paymentsto create a card payment - Request:
{ "title": "Small Gold Pack", "description": "500 gold coins...", "price": { "currencyId": "USD", "naturalAmount": "4.99" }, "quantity": 1, "buyerId": "demo-user-1" }
- Returns
checkoutUrlandtransactionId
For Crypto Payments:
- Calls
GET /internal/storefront/skus/{skuId}/purchaseusing the product's SKU ID - Returns
consentUrl(storefront checkout URL) andcartId
- Calls
-
Frontend redirects user to the checkout URL
-
GameShift checkout page handles the actual payment
When a payment is completed, GameShift sends a webhook to your app:
-
GameShift sends
POST /api/gameshift/webhookwith:- Headers:
webhook-id,webhook-timestamp,webhook-signature - Body:
{ "data": { "type": "payment", "status": "success", "paymentId": "abc123...", "currency": "USD", "amount": "4.99" } }
- Headers:
-
Webhook Handler (
app/api/gameshift/webhook/route.ts):- Verifies the webhook signature using HMAC-SHA256
- Checks if the payment type is "payment" and status is "success"
- Updates the order status to "paid" in the order store
- Returns HTTP 200 to acknowledge receipt
The webhook handler uses HMAC-SHA256 to verify webhook authenticity:
signedContent = `${webhookId}.${webhookTimestamp}.${JSON.stringify(data)}`
computedSignature = HMAC-SHA256(signedContent, base64Decode(WEBHOOK_SECRET))
isValid = (webhookSignature === `v1,${base64Encode(computedSignature)}`)This ensures the webhook actually came from GameShift and wasn't tampered with.
This demo supports two payment flows, and all products support both methods automatically:
- Uses
/nx/paymentsAPI endpoint - Creates a new payment charge with SKU details
- Returns a
checkoutUrlfor card checkout - User pays with credit/debit card on GameShift's hosted page
- Uses
/internal/storefront/skus/{skuId}/purchaseAPI endpoint - Generates a purchase URL for the SKU
- Returns a
consentUrlfor crypto storefront checkout - User pays with USDC, SOL, or other Solana tokens
- All products are SKUs: Products are dynamically loaded from GameShift SKUs
- User chooses payment method: Toggle between "Card" or "Crypto" on each product
- App routes to correct API:
- Card → Creates payment with SKU details via
/nx/payments - Crypto → Gets purchase URL via
/internal/storefront/skus/{skuId}/purchase
- Card → Creates payment with SKU details via
- Both redirect to GameShift hosted checkout
- Webhooks handle completion for both payment methods
Since products ARE SKUs, both payment methods work automatically. Just create SKUs in the GameShift Dashboard and they'll appear with both payment options.
{
referenceId: "demo-user-1",
email: "demo@example.com"
}Products are dynamically loaded from GameShift SKUs. The storefront will display:
- All SKUs created in your GameShift Dashboard
- Product name, description, and price from SKU details
- Payment method selector (Card / Crypto) for each product
- Real-time pricing and availability
No hardcoded products - everything comes from GameShift!
Orders are stored in memory using a simple Map. This means:
- ✅ Perfect for demos and development
- ❌ Data is lost when the server restarts
- ❌ Not suitable for production (use a database instead)
Create a GameShift payment and get the checkout URL. Supports both card and crypto payments.
Request:
{
"productId": "gold-pack-small",
"paymentMethod": "card" // or "crypto" (optional, defaults to "card")
}Response:
{
"checkoutUrl": "https://app.gameshift.dev/checkout?payment=...",
"paymentId": "abc123..."
}Notes:
- If
paymentMethodis "crypto", the product must have askuIdconfigured - Card payments use
/nx/paymentsAPI - Crypto payments use
/internal/storefront/skus/{skuId}/purchaseAPI
Get the status of a payment by ID.
Response:
{
"id": "abc123...",
"status": "Completed",
"purchaser": {
"referenceId": "demo-user-1",
"email": "demo@example.com"
},
"price": {
"currencyId": "USD",
"naturalAmount": "4.99"
}
}Webhook endpoint for GameShift events. Automatically verifies signatures and processes payment completion events.
To test webhooks during local development:
-
Use a tunneling service like ngrok:
ngrok http 3000
-
Configure webhook in GameShift Dashboard:
- URL:
https://your-ngrok-url.ngrok.io/api/gameshift/webhook - Copy the webhook secret to your
.env.local
- URL:
-
Test a payment and watch the console logs to see webhook events
Alternatively, you can use the payment status API endpoint to manually check payment status after completing a test payment.
- ✅ All GameShift API calls are made from server-side only (API routes)
- ✅ The
GAMESHIFT_API_KEYis never exposed to the client - ✅ Environment variables are properly configured in
.env.local(not committed to git)
- ✅ Webhook signatures are verified using HMAC-SHA256
- ✅ Invalid signatures are rejected with HTTP 401
- ✅ Constant-time comparison prevents timing attacks
- ✅ Product IDs are validated before creating payments
- ✅ API errors are handled gracefully
- ✅ Proper HTTP status codes are returned
To use this code in production, you'll need to:
-
Replace in-memory storage with a database (PostgreSQL, MongoDB, etc.)
- Update
lib/demoOrderStore.tsto use your database - Store user data, products, and orders persistently
- Update
-
Implement real user authentication
- Replace hardcoded demo user with actual auth (NextAuth.js, Clerk, etc.)
- Use session/JWT to identify current user
-
Update environment variables
- Use production GameShift API key
- Set production webhook URL
- Use production base URL
-
Add proper error handling and logging
- Implement error monitoring (Sentry, LogRocket, etc.)
- Add detailed logging for payment flows
-
Implement proper order fulfillment
- Actually grant the purchased items to the user's account
- Send confirmation emails
- Handle failed/refunded payments
-
Add tests
- Unit tests for business logic
- Integration tests for API routes
- E2E tests for payment flows
- Check that
GAMESHIFT_API_KEYis set correctly in.env.local - Verify your API key is valid in the GameShift Dashboard
- Check the console logs for detailed error messages
- Ensure
GAMESHIFT_WEBHOOK_SECRETmatches the secret in GameShift Dashboard - Check that the webhook URL is configured correctly
- Verify the secret is base64-encoded
- Check if the webhook is being received (look at server logs)
- Verify the webhook signature is passing validation
- Check if the order exists in the in-memory store (server may have restarted)
This is a reference implementation for educational purposes. Feel free to use it as a starting point for your own projects.
For GameShift-specific questions:
- Visit GameShift Documentation
- Contact GameShift support
For issues with this demo:
- Check the troubleshooting section above
- Review the console logs for detailed error messages