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
- Create a new API key in the dashboard
- Update your applications
- Verify new key works
- 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'),
);