Blog

Construindo uma Plataforma de Atendimento ao Cliente em Redes Sociais

Aprenda como construir uma plataforma de atendimento ao cliente em redes sociais com suporte omnichannel, webhooks em tempo real e respostas com IA usando TypeScript.

Por

+8

Poste em tudo. Uma API.

Try Free

Construindo uma Plataforma de Atendimento ao Cliente em Redes Sociais

Os clientes modernos esperam suporte onde quer que estejam. Uma plataforma robusta de atendimento ao cliente em redes sociais deve agregar conversas do Facebook, Instagram, Twitter, LinkedIn e outras em uma única interface onde as equipes de suporte podem responder de forma rápida e consistente. Este guia apresenta a construção de tal plataforma do zero, cobrindo decisões de arquitetura, infraestrutura em tempo real, colaboração de equipe e assistência com IA.

Ao final deste artigo, você terá código TypeScript funcional para uma plataforma de suporte social ao cliente que lida com agregação de mensagens multicanal, roteamento inteligente, rastreamento de SLA e integração perfeita com CRM.

A Necessidade do Atendimento ao Cliente em Redes Sociais

Os clientes não esperam mais em filas de espera ou enviam emails. Eles tweetam reclamações, enviam DM para marcas no Instagram e deixam comentários em posts do Facebook esperando respostas rápidas. Pesquisas mostram que 40% dos consumidores esperam uma resposta dentro de uma hora nas redes sociais, e 79% esperam uma resposta dentro de 24 horas.

Construir um helpdesk de redes sociais eficaz requer resolver vários desafios técnicos:

  • Fragmentação de canais: Cada plataforma tem APIs, limites de taxa e formatos de dados diferentes
  • Responsividade em tempo real: Os clientes esperam reconhecimento imediato
  • Coordenação de equipe: Múltiplos agentes precisam trabalhar sem interferir uns nos outros
  • Preservação de contexto: Os agentes precisam do histórico do cliente em todos os canais
  • Rastreamento de desempenho: A gestão precisa de visibilidade sobre tempos de resposta e taxas de resolução

Uma API de suporte omnichannel bem arquitetada abstrai essas complexidades, dando à sua equipe uma interface unificada independentemente de onde as conversas se originam.

Visão Geral da Arquitetura da Plataforma

A arquitetura segue um design modular que separa responsabilidades e escala independentemente:

Arquitetura da Plataforma

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

Os componentes principais incluem:

ComponenteResponsabilidadeEstratégia de Escalabilidade
Receptor de WebhookIngerir eventos em tempo real das plataformasHorizontal com balanceador de carga
Agregador de MensagensNormalizar e armazenar conversasRéplicas de leitura para consultas
Motor de RoteamentoAtribuir conversas aos agentesStateless, orientado a eventos
Interface do AgenteGerenciamento de conversas em tempo realConexões WebSocket
Assistente de IAGerar sugestões de respostaWorkers habilitados para GPU
Motor de AnalyticsRastrear métricas e gerar relatóriosBanco de dados de séries temporais

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

Estratégia de Integração Multicanal

Cada plataforma social tem características de API únicas. Uma plataforma de suporte social ao cliente bem-sucedida normaliza essas diferenças enquanto preserva recursos específicos da plataforma.

// 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: As APIs das plataformas têm limites de taxa diferentes. O Facebook permite 200 chamadas por hora por usuário, enquanto os limites do Twitter variam por endpoint. Sempre implemente backoff exponencial e respeite os headers retryAfter.

Infraestrutura de Webhook em Tempo Real para Seu Helpdesk de Redes Sociais

Um helpdesk de redes sociais responsivo requer entrega de mensagens em tempo real. Webhooks eliminam polling e garantem que os agentes vejam novas mensagens instantaneamente.

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

Roteamento e Atribuição de Conversas

Roteamento inteligente

Uma API. 13+ plataformas.

Integre redes sociais em minutos, não semanas.

Criado para desenvolvedores. Adorado por agências. Confiado por 6.325 usuários.