Skip to main content

Overview

Feature Unlock products let users purchase access to premium features in your application. PayGate generates access tokens that you verify via API.

How It Works

┌─────────────────────────────────────────────────────────────┐
│                   Feature Unlock Flow                        │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  1. User purchases feature unlock                            │
│     └─► Pays via x402                                       │
│                                                              │
│  2. PayGate generates access token                           │
│     └─► Format: ft_xxxxxxxxxxxx                             │
│                                                              │
│  3. Webhook sent to your server                              │
│     └─► Contains token + feature IDs                        │
│                                                              │
│  4. Store token for user                                     │
│     └─► Associate with wallet/account                       │
│                                                              │
│  5. User provides token in your app                          │
│     └─► Or auto-retrieve from wallet                        │
│                                                              │
│  6. Your app verifies via API                                │
│     └─► GET /public/feature/verify                          │
│                                                              │
│  7. Enable features if valid                                 │
│     └─► Check feature IDs                                   │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Creating a Feature Unlock Product

Dashboard

  1. Go to ProductsCreate Product
  2. Select type: Feature Unlock
  3. Fill in details:
Title: Pro Plan
Description: Unlock all premium features
Price: 10.00 USDC
Network: mainnet-beta
Feature IDs:
  - pro_analytics
  - pro_export
  - pro_themes
  - unlimited_storage

Webhook Integration

When a user purchases, you receive:
{
  "type": "feature.unlocked",
  "productId": "prod_pro123",
  "productTitle": "Pro Plan",
  "accessToken": "ft_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
  "featureIds": [
    "pro_analytics",
    "pro_export",
    "pro_themes",
    "unlimited_storage"
  ],
  "walletAddress": "7xK3abcdefghijklmnop",
  "txHash": "5xyz...",
  "timestamp": "2024-01-15T10:30:00.000Z"
}

Handling the Webhook

app.post('/api/paygate-webhook', async (req, res) => {
  const { type, accessToken, featureIds, walletAddress } = req.body;

  if (type === 'feature.unlocked') {
    // Find or create user
    let user = await db.users.findUnique({
      where: { wallet: walletAddress }
    });

    if (!user) {
      user = await db.users.create({
        data: { wallet: walletAddress }
      });
    }

    // Store access token and features
    await db.users.update({
      where: { id: user.id },
      data: {
        accessToken,
        features: featureIds,
        upgradedAt: new Date()
      }
    });

    // Send confirmation
    await sendNotification(user, 'Pro features unlocked!');
  }

  res.json({ received: true });
});

Verifying Access Tokens

API Endpoint

POST https://api-paygate.getaether.xyz/public/feature/verify

Request

const response = await fetch(
  'https://api-paygate.getaether.xyz/public/feature/verify',
  {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.PAYGATE_API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      accessToken: 'ft_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6'
    })
  }
);

const result = await response.json();

Response

{
  "valid": true,
  "features": [
    "pro_analytics",
    "pro_export",
    "pro_themes",
    "unlimited_storage"
  ],
  "productId": "prod_pro123",
  "walletAddress": "7xK3abcdefghijklmnop",
  "createdAt": "2024-01-15T10:30:00.000Z"
}

Invalid Token Response

{
  "valid": false,
  "error": "Token not found or revoked"
}

Integration Patterns

Middleware Pattern

async function requireFeature(featureId: string) {
  return async (req, res, next) => {
    const token = req.headers['x-access-token'];

    if (!token) {
      return res.status(401).json({ error: 'Access token required' });
    }

    const response = await fetch(
      'https://api-paygate.getaether.xyz/public/feature/verify',
      {
        method: 'POST',
        headers: {
          'X-API-Key': process.env.PAYGATE_API_KEY,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ accessToken: token })
      }
    );

    const { valid, features } = await response.json();

    if (!valid || !features.includes(featureId)) {
      return res.status(403).json({
        error: 'Feature not available',
        upgrade: `https://paygate.getaether.xyz/pay/prod_pro123`
      });
    }

    next();
  };
}

// Usage
app.get('/api/analytics',
  requireFeature('pro_analytics'),
  async (req, res) => {
    // Pro analytics logic
  }
);

React Hook

import { useState, useEffect } from 'react';

function useFeatures(accessToken: string | null) {
  const [features, setFeatures] = useState<string[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    if (!accessToken) {
      setFeatures([]);
      setLoading(false);
      return;
    }

    async function verify() {
      try {
        const response = await fetch('/api/verify-features', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ accessToken })
        });

        const { valid, features } = await response.json();

        if (valid) {
          setFeatures(features);
        }
      } finally {
        setLoading(false);
      }
    }

    verify();
  }, [accessToken]);

  return {
    features,
    loading,
    hasFeature: (id: string) => features.includes(id)
  };
}

// Usage
function App() {
  const { hasFeature, loading } = useFeatures(user?.accessToken);

  if (loading) return <Loading />;

  return (
    <div>
      {hasFeature('pro_analytics') ? (
        <ProAnalytics />
      ) : (
        <UpgradePrompt />
      )}
    </div>
  );
}

Managing Tokens

List All Tokens

const response = await fetch(
  'https://api-paygate.getaether.xyz/public/feature/tokens',
  {
    headers: {
      'X-API-Key': process.env.PAYGATE_API_KEY
    }
  }
);

const { tokens } = await response.json();
// Returns all tokens for your products

Revoke a Token

await fetch(
  'https://api-paygate.getaether.xyz/public/feature/revoke',
  {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.PAYGATE_API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      accessToken: 'ft_token_to_revoke'
    })
  }
);

Best Practices

  1. Cache verification - Don’t verify on every request
  2. Handle revocation - Listen for token revocation events
  3. Graceful degradation - Show upgrade prompts for missing features
  4. Offline access - Consider local caching for verified features
  5. Security - Never expose access tokens to client-side code