Blog

Costruire una Piattaforma di Customer Service per Social Media

Scopri come costruire una piattaforma di customer service per social media con supporto omnicanale, webhook in tempo reale e risposte basate su AI utilizzando TypeScript.

Di

+8

Pubblica ovunque. Una API.

Try Free

Costruire una Piattaforma di Customer Service per Social Media

I clienti moderni si aspettano supporto ovunque si trovino. Una robusta piattaforma di customer service per social media deve aggregare le conversazioni da Facebook, Instagram, Twitter, LinkedIn e altri canali in un'unica interfaccia dove i team di supporto possono rispondere rapidamente e in modo coerente. Questa guida illustra come costruire una piattaforma di questo tipo da zero, coprendo le decisioni architetturali, l'infrastruttura in tempo reale, la collaborazione del team e l'assistenza basata su AI.

Alla fine di questo articolo, avrai codice TypeScript funzionante per una piattaforma di supporto clienti social che gestisce l'aggregazione di messaggi multi-canale, il routing intelligente, il tracciamento degli SLA e l'integrazione seamless con il CRM.

La Necessità del Customer Service Social

I clienti non aspettano più in attesa al telefono o inviano email. Twittano lamentele, inviano DM ai brand su Instagram e lasciano commenti sui post di Facebook aspettandosi risposte rapide. Le ricerche mostrano che il 40% dei consumatori si aspetta una risposta entro un'ora sui social media, e il 79% si aspetta una risposta entro 24 ore.

Costruire un helpdesk per social media efficace richiede di risolvere diverse sfide tecniche:

  • Frammentazione dei canali: Ogni piattaforma ha API, limiti di frequenza e formati dati diversi
  • Reattività in tempo reale: I clienti si aspettano un riconoscimento immediato
  • Coordinamento del team: Più agenti devono lavorare senza sovrapporsi
  • Preservazione del contesto: Gli agenti hanno bisogno dello storico del cliente su tutti i canali
  • Tracciamento delle performance: Il management ha bisogno di visibilità sui tempi di risposta e i tassi di risoluzione

Un'API di supporto omnicanale ben architettata astrae queste complessità, fornendo al tuo team un'interfaccia unificata indipendentemente da dove originano le conversazioni.

Panoramica dell'Architettura della Piattaforma

L'architettura segue un design modulare che separa le responsabilità e scala in modo indipendente:

Architettura della Piattaforma

// 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;
}

I componenti principali includono:

ComponenteResponsabilitàStrategia di Scaling
Webhook ReceiverAcquisire eventi in tempo reale dalle piattaformeOrizzontale con load balancer
Message AggregatorNormalizzare e memorizzare le conversazioniRead replica per le query
Routing EngineAssegnare le conversazioni agli agentiStateless, event-driven
Agent InterfaceGestione delle conversazioni in tempo realeConnessioni WebSocket
AI AssistantGenerare suggerimenti di rispostaWorker abilitati GPU
Analytics EngineTracciare metriche e generare reportDatabase time-series

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

Strategia di Integrazione Multi-Canale

Ogni piattaforma social ha caratteristiche API uniche. Una piattaforma di supporto clienti social di successo normalizza queste differenze preservando le funzionalità specifiche della piattaforma.

// 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;
    });
  }
}

Nota: Le API delle piattaforme hanno limiti di frequenza diversi. Facebook consente 200 chiamate all'ora per utente, mentre i limiti di Twitter variano in base all'endpoint. Implementa sempre il backoff esponenziale e rispetta gli header retryAfter.

Infrastruttura Webhook in Tempo Reale per il Tuo Helpdesk Social Media

Un helpdesk per social media reattivo richiede la consegna dei messaggi in tempo reale. I webhook eliminano il polling e assicurano che gli agenti vedano i nuovi messaggi istantaneamente.

// 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);
}

Routing e Assegnazione delle Conversazioni

Il routing intelligente

Una API. 13+ piattaforme.

Integra i social in minuti, non settimane.

Progettato per sviluppatori. Apprezzato dalle agenzie. Fidato da 6.325 utenti.