Skip to main content
Follow these best practices to build secure, high-performance, and user-friendly AI services with Nevermined.

Security

This is the most critical security practice. Before processing any request to a protected endpoint, you must validate the incoming request to ensure the user is an authorized subscriber with sufficient credits.
const isValid = await payments.isValidRequest(
  planId, 
  agentId, 
  subscriberAddress, 
  signature
);

if (!isValid) {
  // Always return a 402 Payment Required response
  const paymentCard = await payments.getAgentPaymentCard(agentId);
  return response.status(402).json(paymentCard);
}
Never hardcode API keys or other secrets in your source code. Use environment variables to manage your credentials securely.
// Good ✅
const payments = Payments.getInstance({
  nvmApiKey: process.env.NVM_API_KEY,
});

// Bad ❌
const payments = Payments.getInstance({
  nvmApiKey: 'sk_live_abcd1234...',
});
Protect your agent from denial-of-service (DoS) attacks and abuse by implementing rate limiting. This ensures fair usage and maintains service availability.
import rateLimit from 'express-rate-limit';

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per window
  message: 'Too many requests. Please try again later.'
});

app.use('/api/', limiter);
In case of an unexpected error during the validation process, your application should “fail closed”—meaning it should deny access by default rather than granting it.
const isValid = await payments.isValidRequest(
  planId, agentId, subscriber, signature
).catch(error => {
  console.error('CRITICAL: Validation check failed with error:', error);
  return false; // Fail closed for security
});

Performance

Your agent’s metadata and its payment plans don’t change frequently. Cache this data in your application to reduce latency and minimize API calls to Nevermined.
const cache = new Map();

async function getCachedAgent(agentId: string) {
  const cacheKey = `agent:${agentId}`;
  if (!cache.has(cacheKey)) {
    const agent = await payments.getAgent(agentId);
    // Cache for 5 minutes
    cache.set(cacheKey, agent, 300); 
  }
  return cache.get(cacheKey);
}
When you need to perform the same operation multiple times (e.g., fetching details for several agents), use Promise.all to run them in parallel instead of sequentially.
// Good ✅ (Parallel execution)
const [agent1, agent2] = await Promise.all([
  payments.getAgent(agentId1),
  payments.getAgent(agentId2)
]);

// Bad ❌ (Sequential execution)
const agent1 = await payments.getAgent(agentId1);
const agent2 = await payments.getAgent(agentId2);

User Experience

When a user without access tries to use your service, provide a clear error message along with the paymentCard so they know exactly how to gain access.
if (!isValid) {
  const paymentCard = await payments.getAgentPaymentCard(agentId);
  const agent = await getCachedAgent(agentId); // Use cached data
  
  return response.status(402).json({
    error: 'Payment required to access this service.',
    message: 'Please purchase one of the available plans to continue.',
    paymentCard,
    availablePlans: agent.plans.map(p => ({
      name: p.metadata.name,
      price: p.price.amounts[0],
      // Add other relevant plan details
    }))
  });
}
Use response headers to provide subscribers with real-time information about their credit balance. This transparency helps build trust and improves the user experience.
const balance = await payments.getPlanBalance(planId, subscriberAddress);

response.setHeader('X-Credits-Remaining', balance.credits.toString());

if (balance.credits < 10) {
  response.setHeader('X-Low-Balance-Warning', 'Your credits are running low.');
}

Implementation Patterns

Middleware Pattern

Create reusable middleware for authentication:
function neverminedAuth(planId: string, agentId: string) {
  return async (req: Request, res: Response, next: NextFunction) => {
    const isValid = await payments.isValidRequest(
      planId,
      agentId,
      req.body.subscriberAddress,
      req.headers['x-nvm-query-signature']
    )

    if (!isValid) {
      const paymentCard = await payments.getAgentPaymentCard(agentId)
      return res.status(402).json(paymentCard)
    }

    // Add usage info to request object
    req.nevermined = { planId, agentId }
    next()
  }
}

// Usage
app.post('/api/query', 
  neverminedAuth(planId, agentId), 
  handleQuery
)

Error Handling Pattern

Implement comprehensive error handling:
import { PaymentsError } from '@nevermined-io/payments'

app.use((error, req, res, next) => {
  if (error instanceof PaymentsError) {
    switch (error.code) {
      case 'INVALID_API_KEY':
        return res.status(401).json({ error: 'Invalid API key' })
      case 'INSUFFICIENT_BALANCE':
        return res.status(402).json({ error: 'Insufficient balance' })
      case 'INVALID_PLAN_CONFIG':
        return res.status(400).json({ error: 'Invalid plan configuration' })
      case 'NETWORK_ERROR':
        return res.status(503).json({ error: 'Service temporarily unavailable' })
      default:
        return res.status(500).json({ error: 'Internal server error' })
    }
  }
  
  // Handle other errors
  console.error('Unexpected error:', error)
  res.status(500).json({ error: 'Internal server error' })
})

Monitoring and Analytics

Track Revenue

Monitor your agent’s revenue generation:
async function trackRevenue(planId: string) {
  const plan = await payments.getPlan(planId)
  const price = plan.price.amounts[0]
  const token = plan.price.tokenAddress
  
  // Log or send to analytics
  console.log(`Revenue: ${price} tokens from ${token}`)
  
  // Optional: Send to analytics service
  analytics.track('revenue_generated', {
    planId,
    amount: price.toString(),
    token,
    timestamp: new Date().toISOString()
  })
}

Monitor Agent Health

Implement health checks and monitoring:
app.get('/health', async (req, res) => {
  try {
    // Check Nevermined connection
    const agent = await payments.getAgent(agentId)
    
    res.json({
      status: 'healthy',
      agent: {
        id: agent.agentId,
        name: agent.metadata.name,
        plans: agent.plans.length
      }
    })
  } catch (error) {
    res.status(503).json({
      status: 'unhealthy',
      error: error.message
    })
  }
})

Testing Recommendations

Use Testing Environment

Always use the testing environment during development:
const payments = Payments.getInstance({
  nvmApiKey: process.env.NVM_TEST_API_KEY,
  environment: 'testing' // Base Sepolia testnet
})

Mock for Unit Tests

Mock the Payments SDK for unit testing:
jest.mock('@nevermined-io/payments', () => ({
  Payments: {
    getInstance: jest.fn().mockReturnValue({
      isValidRequest: jest.fn().mockResolvedValue(true),
      redeemCredits: jest.fn().mockResolvedValue(true),
      getAgentPaymentCard: jest.fn().mockResolvedValue({
        agentId: 'test-agent',
        plans: []
      })
    })
  }
}))

Integration Testing

Test the full flow in a test environment:
describe('Agent Integration', () => {
  let agentId: string
  let planId: string

  beforeAll(async () => {
    // Create test agent and plan
    const result = await payments.registerAgentAndPlan(
      testMetadata,
      testApi,
      testPrice,
      testCredits
    )
    agentId = result.agentId
    planId = result.planId
  })

  test('should validate and process requests', async () => {
    // Test implementation
  })
})

Summary

Following these best practices will help you build secure, performant, and user-friendly AI agents with Nevermined Payments. Remember to:
  • Always validate requests before processing
  • Secure your API keys and sensitive data
  • Implement proper error handling
  • Cache data for better performance
  • Provide clear feedback to users
  • Monitor your agents’ health and usage
  • Test thoroughly in development environments
For more detailed examples and advanced patterns, check out our Example Apps section.