Here are five code patterns you'll use constantly when building with Ferni.
1. Session Initialization
Always initialize your session with proper error handling:
import { FerniClient } from '@ferni/sdk';
const client = new FerniClient({
apiKey: process.env.FERNI_API_KEY,
// Enable automatic reconnection
reconnect: true,
reconnectAttempts: 3,
});
// Handle connection events
client.on('connected', () => console.log('Session ready'));
client.on('disconnected', (reason) => {
console.log(`Disconnected: ${reason}`);
});
Pro tip: Set reconnect: true for production - it handles network blips gracefully.
2. Webhook Signature Verification
Never skip this step in production:
import { verifyWebhookSignature } from '@ferni/sdk';
app.post('/webhook', (req, res) => {
const signature = req.headers['x-ferni-signature'];
const timestamp = req.headers['x-ferni-timestamp'];
const isValid = verifyWebhookSignature({
payload: req.body,
signature,
timestamp,
secret: process.env.WEBHOOK_SECRET,
// Reject requests older than 5 minutes
maxAge: 300,
});
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook...
});
3. Graceful Conversation Handoff
Transfer context smoothly between personas:
// In your MCP server
async function handoffToSpecialist(context, targetPersona) {
// Preserve conversation state
const summary = await context.summarizeConversation();
return {
action: 'handoff',
target: targetPersona,
context: {
summary,
userMood: context.detectedMood,
pendingTopics: context.unaddressedTopics,
},
};
}
4. Streaming Response Handler
Handle voice responses without blocking:
const stream = await client.streamResponse(userInput);
for await (const chunk of stream) {
switch (chunk.type) {
case 'transcript':
console.log('Speaking:', chunk.text);
break;
case 'emotion':
updateAvatarExpression(chunk.emotion);
break;
case 'action':
executeAction(chunk.action);
break;
}
}
5. Error Boundaries for Voice
Wrap voice interactions with fallbacks:
async function safeVoiceInteraction(input) {
try {
return await client.process(input);
} catch (error) {
if (error.code === 'CONTEXT_LIMIT') {
// Summarize and continue
await client.compactContext();
return await client.process(input);
}
if (error.code === 'RATE_LIMITED') {
// Graceful degradation
return {
response: "I need a moment to catch up. One second...",
retry: true,
retryAfter: error.retryAfter,
};
}
throw error;
}
}
Next Steps
Happy building!