Construire une plateforme de service client sur les réseaux sociaux
Les clients modernes attendent une assistance où qu'ils soient. Une plateforme robuste de service client sur les réseaux sociaux doit agréger les conversations de Facebook, Instagram, Twitter, LinkedIn et d'autres plateformes dans une interface unique où les équipes de support peuvent répondre rapidement et de manière cohérente. Ce guide vous accompagne dans la construction d'une telle plateforme de A à Z, couvrant les décisions d'architecture, l'infrastructure en temps réel, la collaboration d'équipe et l'assistance alimentée par l'IA.
À la fin de cet article, vous disposerez d'un code TypeScript fonctionnel pour une plateforme de support client social qui gère l'agrégation de messages multi-canaux, le routage intelligent, le suivi des SLA et l'intégration CRM transparente.
Le besoin d'un service client social
Les clients n'attendent plus en ligne ou n'envoient plus d'emails. Ils tweetent leurs plaintes, envoient des DM aux marques sur Instagram et laissent des commentaires sur les publications Facebook en attendant des réponses rapides. Les recherches montrent que 40% des consommateurs attendent une réponse dans l'heure sur les réseaux sociaux, et 79% attendent une réponse dans les 24 heures.
Construire un helpdesk pour réseaux sociaux efficace nécessite de résoudre plusieurs défis techniques :
- Fragmentation des canaux : Chaque plateforme a des API, des limites de débit et des formats de données différents
- Réactivité en temps réel : Les clients attendent un accusé de réception immédiat
- Coordination d'équipe : Plusieurs agents doivent travailler sans se marcher sur les pieds
- Préservation du contexte : Les agents ont besoin de l'historique client sur tous les canaux
- Suivi des performances : La direction a besoin de visibilité sur les temps de réponse et les taux de résolution
Une API de support omnicanal bien architecturée abstrait ces complexités, offrant à votre équipe une interface unifiée quelle que soit l'origine des conversations.
Vue d'ensemble de l'architecture de la plateforme
L'architecture suit une conception modulaire qui sépare les responsabilités et évolue indépendamment :

// types/customer-service.ts
export interface SupportPlatformConfig {
platforms: SupportedPlatform[];
webhookEndpoint: string;
routingStrategy: 'round-robin' | 'skill-based' | 'load-balanced';
slaDefaults: SLAConfiguration;
aiAssistEnabled: boolean;
}
export interface SupportedPlatform {
name: 'facebook' | 'instagram' | 'twitter' | 'linkedin' | 'bluesky' | 'telegram';
features: ('messages' | 'comments' | 'reviews')[];
credentials: PlatformCredentials;
webhookSecret?: string;
}
export interface Conversation {
id: string;
platform: string;
accountId: string;
customerId: string;
customerName: string;
customerHandle: string;
customerAvatarUrl?: string;
status: 'open' | 'pending' | 'resolved' | 'closed';
priority: 'low' | 'medium' | 'high' | 'urgent';
assignedTo?: string;
tags: string[];
messages: Message[];
metadata: ConversationMetadata;
createdAt: Date;
updatedAt: Date;
firstResponseAt?: Date;
resolvedAt?: Date;
}
export interface Message {
id: string;
conversationId: string;
direction: 'inbound' | 'outbound';
content: string;
contentType: 'text' | 'image' | 'video' | 'attachment';
attachments?: Attachment[];
senderId: string;
senderType: 'customer' | 'agent' | 'bot';
platform: string;
platformMessageId: string;
timestamp: Date;
deliveryStatus?: 'sent' | 'delivered' | 'read' | 'failed';
}
export interface SLAConfiguration {
firstResponseMinutes: number;
resolutionHours: number;
businessHoursOnly: boolean;
businessHours: {
timezone: string;
schedule: WeeklySchedule;
};
}
export interface ConversationMetadata {
source: 'dm' | 'comment' | 'mention' | 'review';
postId?: string;
postUrl?: string;
sentiment?: 'positive' | 'neutral' | 'negative';
language?: string;
crmContactId?: string;
previousConversations?: number;
}
Les composants principaux incluent :
| Composant | Responsabilité | Stratégie de mise à l'échelle |
|---|---|---|
| Récepteur de webhook | Ingérer les événements en temps réel des plateformes | Horizontal avec équilibreur de charge |
| Agrégateur de messages | Normaliser et stocker les conversations | Réplicas en lecture pour les requêtes |
| Moteur de routage | Assigner les conversations aux agents | Sans état, piloté par événements |
| Interface agent | Gestion des conversations en temps réel | Connexions WebSocket |
| Assistant IA | Générer des suggestions de réponses | Workers avec GPU |
| Moteur d'analytique | Suivre les métriques et générer des rapports | Base de données de séries temporelles |
Stratégie d'intégration multi-canaux
Chaque plateforme sociale a des caractéristiques API uniques. Une plateforme de support client social réussie normalise ces différences tout en préservant les fonctionnalités spécifiques à chaque plateforme.
// services/platform-adapter.ts
import { INBOX_PLATFORMS, isPlatformSupported } from '@/libs/inbox/platforms';
export interface PlatformAdapter {
platform: string;
fetchConversations(accountId: string, since?: Date): Promise<Conversation[]>;
fetchMessages(conversationId: string, cursor?: string): Promise<Message[]>;
sendMessage(conversationId: string, content: MessageContent): Promise<Message>;
markAsRead(conversationId: string): Promise<void>;
}
export class MultiChannelAdapter {
private adapters: Map<string, PlatformAdapter> = new Map();
constructor(private config: SupportPlatformConfig) {
this.initializeAdapters();
}
private initializeAdapters(): void {
for (const platform of this.config.platforms) {
if (!isPlatformSupported(platform.name, 'messages')) {
console.warn(`Platform ${platform.name} does not support direct messages`);
continue;
}
const adapter = this.createAdapter(platform);
this.adapters.set(platform.name, adapter);
}
}
private createAdapter(platform: SupportedPlatform): PlatformAdapter {
// Factory pattern for platform-specific implementations
switch (platform.name) {
case 'facebook':
return new FacebookAdapter(platform.credentials);
case 'instagram':
return new InstagramAdapter(platform.credentials);
case 'twitter':
return new TwitterAdapter(platform.credentials);
case 'linkedin':
return new LinkedInAdapter(platform.credentials);
case 'bluesky':
return new BlueskyAdapter(platform.credentials);
case 'telegram':
return new TelegramAdapter(platform.credentials);
default:
throw new Error(`Unsupported platform: ${platform.name}`);
}
}
async aggregateConversations(
accountIds: string[],
options: AggregationOptions
): Promise<AggregatedConversations> {
const results: Conversation[] = [];
const errors: AggregationError[] = [];
const fetchPromises = accountIds.map(async (accountId) => {
const account = await this.getAccount(accountId);
const adapter = this.adapters.get(account.platform);
if (!adapter) {
return {
accountId,
conversations: [],
error: { message: `No adapter for ${account.platform}` }
};
}
try {
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => reject(new Error('Request timeout')), 10000);
});
const conversations = await Promise.race([
adapter.fetchConversations(accountId, options.since),
timeoutPromise
]);
return { accountId, conversations, error: null };
} catch (error: any) {
return {
accountId,
conversations: [],
error: {
message: error.message,
code: error.code,
retryAfter: error.retryAfter
}
};
}
});
const settledResults = await Promise.all(fetchPromises);
for (const result of settledResults) {
if (result.error) {
errors.push({
accountId: result.accountId,
platform: 'unknown',
error: result.error.message,
code: result.error.code,
retryAfter: result.error.retryAfter
});
} else {
results.push(...result.conversations);
}
}
// Deduplicate by conversation ID
const uniqueConversations = this.deduplicateConversations(results);
// Sort by most recent activity
uniqueConversations.sort((a, b) =>
new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
);
return {
conversations: uniqueConversations,
meta: {
accountsQueried: accountIds.length,
accountsFailed: errors.length,
failedAccounts: errors,
lastUpdated: new Date().toISOString()
}
};
}
private deduplicateConversations(conversations: Conversation[]): Conversation[] {
const seen = new Set<string>();
return conversations.filter(conv => {
const key = `${conv.platform}_${conv.customerId}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
}
}
Note : Les API des plateformes ont des limites de débit différentes. Facebook autorise 200 appels par heure par utilisateur, tandis que les limites de Twitter varient selon l'endpoint. Implémentez toujours un backoff exponentiel et respectez les en-têtes
retryAfter.
Infrastructure webhook en temps réel pour votre helpdesk de réseaux sociaux
Un helpdesk pour réseaux sociaux réactif nécessite une livraison de messages en temps réel. Les webhooks éliminent le polling et garantissent que les agents voient les nouveaux messages instantanément.
// api/webhooks/social/route.ts
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';
import { EventEmitter } from 'events';
const webhookEmitter = new EventEmitter();
interface WebhookPayload {
platform: string;
eventType: string;
data: any;
timestamp: string;
signature?: string;
}
export async function POST(request: NextRequest) {
const platform = request.nextUrl.searchParams.get('platform');
if (!platform) {
return NextResponse.json(
{ error: 'Platform parameter required' },
{ status: 400 }
);
}
try {
const body = await request.text();
const signature = request.headers.get('x-webhook-signature');
// Verify webhook signature
const isValid = await verifyWebhookSignature(platform, body, signature);
if (!isValid) {
console.error(`Invalid webhook signature for ${platform}`);
return NextResponse.json(
{ error: 'Invalid signature' },
{ status: 401 }
);
}
const payload = JSON.parse(body);
const normalizedEvent = normalizeWebhookEvent(platform, payload);
// Process asynchronously to respond quickly
processWebhookEvent(normalizedEvent).catch(error => {
console.error('Webhook processing error:', error);
});
// Emit for real-time listeners
webhookEmitter.emit('message', normalizedEvent);
return NextResponse.json({ received: true });
} catch (error: any) {
console.error('Webhook error:', error);
return NextResponse.json(
{ error: 'Processing failed' },
{ status: 500 }
);
}
}
async function verifyWebhookSignature(
platform: string,
body: string,
signature: string | null
): Promise<boolean> {
if (!signature) return false;
const secret = process.env[`${platform.toUpperCase()}_WEBHOOK_SECRET`];
if (!secret) {
console.warn(`No webhook secret configured for ${platform}`);
return false;
}
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
function normalizeWebhookEvent(platform: string, payload: any): NormalizedEvent {
switch (platform) {
case 'facebook':
case 'instagram':
return normalizeFacebookEvent(payload);
case 'twitter':
return normalizeTwitterEvent(payload);
case 'linkedin':
return normalizeLinkedInEvent(payload);
default:
throw new Error(`Unknown platform: ${platform}`);
}
}
function normalizeFacebookEvent(payload: any): NormalizedEvent {
const entry = payload.entry?.[0];
const messaging = entry?.messaging?.[0];
if (!messaging) {
return { type: 'unknown', data: payload };
}
return {
type: 'new_message',
platform: payload.object === 'instagram' ? 'instagram' : 'facebook',
conversationId: messaging.sender.id,
message: {
id: messaging.message.mid,
content: messaging.message.text,
senderId: messaging.sender.id,
timestamp: new Date(messaging.timestamp)
},
accountId: entry.id
};
}
async function processWebhookEvent(event: NormalizedEvent): Promise<void> {
if (event.type !== 'new_message') return;
// Find or create conversation
const conversation = await findOrCreateConversation(event);
// Add message to conversation
await addMessageToConversation(conversation.id, event.message);
// Trigger routing if new conversation
if (conversation.isNew) {
await routeConversation(conversation);
}
// Update SLA tracking
await updateSLATracking(conversation.id);
// Generate AI suggestions if enabled
if (process.env.AI_ASSIST_ENABLED === 'true') {
await generateResponseSuggestions(conversation.id, event.message);
}
// Notify assigned agent via WebSocket
await notifyAgent(conversation.assignedTo, {
type: 'new_message',
conversationId: conversation.id,
message: event.message
});
}
export function subscribeToWebhooks(
callback: (event: NormalizedEvent) => void
): () => void {
webhookEmitter.on('message', callback);
return () => webhookEmitter.off('message', callback);
}
Routage et assignation des conversations
Le routage intelligent