Blog

Construire une plateforme de service client sur les réseaux sociaux

Apprenez à construire une plateforme de service client sur les réseaux sociaux avec support omnicanal, webhooks en temps réel et réponses alimentées par l'IA en utilisant TypeScript.

Par

+8

Publiez partout. Une API.

Try Free

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 :

Architecture de la plateforme

// 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 :

ComposantResponsabilitéStratégie de mise à l'échelle
Récepteur de webhookIngérer les événements en temps réel des plateformesHorizontal avec équilibreur de charge
Agrégateur de messagesNormaliser et stocker les conversationsRéplicas en lecture pour les requêtes
Moteur de routageAssigner les conversations aux agentsSans état, piloté par événements
Interface agentGestion des conversations en temps réelConnexions WebSocket
Assistant IAGénérer des suggestions de réponsesWorkers avec GPU
Moteur d'analytiqueSuivre les métriques et générer des rapportsBase de données de séries temporelles

Build faster with Late

One API call to post everywhere. No OAuth headaches. No platform-specific code.

Free tier • No credit card • 99.97% uptime

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

Une API. 13+ plateformes.

Intégrez les réseaux sociaux en minutes, pas en semaines.

Conçu pour les développeurs. Apprécié par les agences. Fiable pour 6 325 utilisateurs.