Skip to main content

Best Practices

Recommended patterns and practices for building robust integrations.

API Key Security

Store Keys Securely

# .env
MAES_API_KEY=sk_live_xxxxx
MAES_PROJECT_ID=your-project-id
import { MaesClient } from '@nuvoni/maes-sdk';

const client = new MaesClient({
apiKey: process.env.MAES_API_KEY!,
projectId: process.env.MAES_PROJECT_ID!,
});

Never Expose in Client-Side Code

// ❌ Wrong - Key visible in browser
// Frontend code
const client = new MaesClient({ apiKey: 'sk_live_xxxxx' });

// ✅ Right - Call through your backend
// Your backend handles MAES API calls

Rotate Keys Regularly

  1. Create a new API key in the dashboard
  2. Update your applications
  3. Verify new key works
  4. Revoke old key

Error Handling

Always Handle Errors

import {
MaesClient,
MaesNotFoundError,
MaesRateLimitError,
} from '@nuvoni/maes-sdk';

async function enableCard(cardId: string) {
try {
return await client.cards.enable(cardId);
} catch (error) {
if (error instanceof MaesNotFoundError) {
console.error('Card not found');
return null;
}

if (error instanceof MaesRateLimitError) {
console.error('Rate limited - retrying...');
await sleep(60000);
return enableCard(cardId);
}

throw error;
}
}

Implement Retry Logic

async function fetchWithRetry<T>(
fn: () => Promise<T>,
maxRetries = 3,
): Promise<T> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxRetries - 1) throw error;

const delay = Math.pow(2, attempt) * 1000;
await sleep(delay);
}
}
throw new Error('Max retries exceeded');
}

Webhooks

Verify Signatures

Always verify webhook signatures:

import { MaesClient, MaesWebhookSignatureError } from '@nuvoni/maes-sdk';

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
try {
const event = client.webhooks.verifySignature(
req.body.toString(),
req.headers['x-webhook-signature'] as string,
process.env.WEBHOOK_SECRET!,
);

handleEvent(event);
res.status(200).send('OK');
} catch (error) {
if (error instanceof MaesWebhookSignatureError) {
return res.status(401).send('Invalid signature');
}
throw error;
}
});

Process Asynchronously

Respond quickly and process in the background:

app.post('/webhook', (req, res) => {
// Verify signature first
const event = client.webhooks.verifySignature(/* ... */);

// Queue for processing
eventQueue.add(event);

// Respond immediately
res.status(200).send('OK');
});

// Process in background
eventQueue.process(async (job) => {
await handleEvent(job.data);
});

Handle Idempotency

Events may be delivered more than once:

const processedEvents = new Set<string>();

async function handleEvent(event) {
if (processedEvents.has(event.id)) {
return; // Already processed
}

processedEvents.add(event.id);
// Process event...
}

For production, store processed event IDs in a database.

Rate Limiting

Batch Operations Efficiently

// ❌ Bad - Rapid sequential requests
for (const cardId of cardIds) {
await client.cards.enable(cardId);
}

// ✅ Good - With delays
for (const cardId of cardIds) {
await client.cards.enable(cardId);
await sleep(200); // 200ms between requests
}

Caching

Cache Card Data

const cardCache = new Map<string, { data: Card; timestamp: number }>();
const CACHE_TTL = 60000; // 1 minute

async function getCard(cardId: string) {
const cached = cardCache.get(cardId);

if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.data;
}

const card = await client.cards.get(cardId);
cardCache.set(cardId, { data: card, timestamp: Date.now() });

return card;
}

// Invalidate on webhook events
function handleWebhook(event) {
if (event.event.startsWith('card.')) {
cardCache.delete(event.data.card_id);
}
}

Environment Configuration

Separate Keys by Environment

import { MaesClient } from '@nuvoni/maes-sdk';

const client = new MaesClient({
apiKey:
process.env.NODE_ENV === 'production'
? process.env.MAES_LIVE_KEY!
: process.env.MAES_SANDBOX_KEY!,
projectId: process.env.MAES_PROJECT_ID!,
});

// SDK automatically detects environment from key prefix
console.log(client.environment); // 'sandbox' or 'production'

Logging

Log API Interactions

async function loggedRequest<T>(
operation: string,
fn: () => Promise<T>,
): Promise<T> {
const requestId = generateId();

console.log(`[${requestId}] Starting: ${operation}`);

try {
const result = await fn();
console.log(`[${requestId}] Completed: ${operation}`);
return result;
} catch (error) {
console.error(`[${requestId}] Failed: ${operation}`, error);
throw error;
}
}

// Usage
const card = await loggedRequest('enable-card', () =>
client.cards.enable('card-id'),
);