Vinicius Aguiar
Case Study

Case Study: X-Drop — como construí um SaaS de dropshipping que faturou R$ 30k+ em 2 meses

16 de abril de 2026 · 12 min de leitura

A X-Drop é uma plataforma SaaS de gestão para operações de dropshipping. Em apenas 2 meses de operação, alcançamos R$ 30k+ em faturamento processado, 100+ usuários ativos, 400+ pedidos e um MRR na casa dos R$ 20k. Neste case study, vou compartilhar as decisões de arquitetura, os desafios técnicos reais e os trade-offs que enfrentamos para chegar nesses números.

O problema

Vendedores de dropshipping operam em múltiplos marketplaces simultaneamente — Mercado Livre, Shopee, entre outros. Cada plataforma tem sua própria API, seus próprios fluxos de pedido, e seus próprios formatos de dados. Sem uma ferramenta centralizada, o vendedor precisa alternar entre 3-4 painéis diferentes, conciliar estoque manualmente e lidar com múltiplos gateways de pagamento. É um processo que não escala.

A X-Drop resolve isso: um único painel que integra catálogo, pedidos, expedição, pagamentos e relatórios financeiros em tempo real — com governança por perfis de acesso para equipes.

Arquitetura e stack

A stack foi escolhida com dois critérios: velocidade de iteração (precisávamos lançar rápido) e capacidade de escalar sem reescrever tudo em 6 meses.

  • Frontend: React + Next.js com TypeScript — SSR para SEO do painel público, CSR para o dashboard interno
  • Backend: NestJS (Node.js) com API REST — módulos isolados por domínio (pedidos, catálogo, financeiro, integrações)
  • Banco: PostgreSQL + Firebase (auth e real-time listeners)
  • Infra: AWS com containers Docker, CI/CD automatizado
  • Pagamentos: Asaas (PIX, boleto) + Mercado Pago (cartão, PIX)
  • Marketplaces: Mercado Livre API + Shopee API

Desafio #1: integração multi-marketplace

Cada marketplace tem uma API completamente diferente. O Mercado Livre usa OAuth 2.0 com tokens de curta duração e webhooks para notificações. A Shopee tem seu próprio sistema de autenticação com assinatura HMAC. Os formatos de pedido, status e categorias são incompatíveis entre si.

A solução foi criar uma camada de abstração por marketplace — adapters que normalizam os dados para um schema interno unificado. Cada adapter implementa a mesma interface (syncProducts, syncOrders, updateInventory), mas lida internamente com as particularidades de cada API.

// Interface comum para todos os marketplaces
interface MarketplaceAdapter {
  syncProducts(sellerId: string): Promise<Product[]>
  syncOrders(sellerId: string, since: Date): Promise<Order[]>
  updateInventory(productId: string, quantity: number): Promise<void>
  mapStatus(externalStatus: string): InternalOrderStatus
}

// Cada marketplace implementa sua própria versão
class MercadoLivreAdapter implements MarketplaceAdapter {
  async syncOrders(sellerId: string, since: Date) {
    const token = await this.refreshToken(sellerId)
    const raw = await this.api.get('/orders/search', { seller: sellerId, since })
    return raw.results.map(order => this.normalizeOrder(order))
  }
}

class ShopeeAdapter implements MarketplaceAdapter {
  async syncOrders(sellerId: string, since: Date) {
    const signature = this.generateHMAC(sellerId, timestamp)
    const raw = await this.api.get('/order/get_order_list', { sign: signature })
    return raw.order_list.map(order => this.normalizeOrder(order))
  }
}

Esse pattern nos permitiu adicionar novos marketplaces sem tocar no core da aplicação. Quando um marketplace muda sua API (o que acontece com frequência), o impacto fica contido no adapter.

Desafio #2: múltiplos gateways de pagamento

Precisávamos suportar Asaas e Mercado Pago simultaneamente — cada vendedor pode escolher seu gateway preferido. Os desafios:

  • Webhooks diferentes: cada gateway notifica em formatos distintos e com diferentes garantias de entrega
  • Idempotência: um mesmo pagamento pode gerar múltiplos webhooks (retry do gateway). Sem controle, você processa o mesmo pagamento duas vezes
  • Conciliação: o saldo reportado pelo gateway nem sempre bate com o que você calculou internamente
  • PIX: fluxo assíncrono com janela de expiração — o status muda de 'pending' para 'paid' ou 'expired' via webhook

A solução seguiu o mesmo princípio dos marketplaces: adapter pattern + uma tabela de eventos de pagamento com chave de idempotência. Cada webhook recebido é registrado com um hash único. Se o mesmo evento chega duas vezes, o segundo é descartado antes de qualquer processamento.

async function handlePaymentWebhook(provider: string, payload: unknown) {
  const adapter = getPaymentAdapter(provider) // 'asaas' | 'mercadopago'
  const event = adapter.parseWebhook(payload)

  // Idempotência: checa se esse evento já foi processado
  const idempotencyKey = `${provider}:${event.externalId}:${event.type}`
  const exists = await db.paymentEvent.findUnique({ where: { idempotencyKey } })
  if (exists) return { status: 'already_processed' }

  // Registra o evento e processa
  await db.$transaction(async (tx) => {
    await tx.paymentEvent.create({ data: { idempotencyKey, ...event } })
    await tx.order.update({
      where: { id: event.orderId },
      data: { paymentStatus: event.status },
    })
  })
}

Desafio #3: escala e observabilidade

Com 100+ usuários e 400+ pedidos em 2 meses, começamos a sentir os primeiros sinais de que decisões de arquitetura importam. Queries de relatório que levavam 50ms começaram a levar 800ms. Sincronizações de marketplace que rodavam em background começaram a competir por conexões do pool de banco.

As ações que tomamos:

  1. Índices compostos nas tabelas de pedidos e transações — queries de relatório voltaram para < 100ms
  2. Connection pooling com limites separados para operações síncronas (API) e assíncronas (sync de marketplace)
  3. Rate limiting por seller nas chamadas aos marketplaces — evita que um vendedor com catálogo grande consuma toda a cota da API
  4. Logs estruturados com contexto de operação (sellerId, marketplace, orderId) — sem isso, debugar um problema em produção entre 3 marketplaces e 2 gateways é impossível
  5. Health checks com métricas de latência por integração — se o tempo de resposta do Mercado Livre passa de 2s, recebemos alerta antes do usuário reclamar

Desafio #4: governança e multi-tenancy

A X-Drop atende vendedores com equipes. Um dono de loja precisa dar acesso a funcionários que processam pedidos, mas sem expor dados financeiros ou configurações de integração. Implementamos RBAC (Role-Based Access Control) com 3 níveis: admin, operador e visualizador.

Cada request passa por um middleware que valida o tenant (seller) e a role do usuário. Dados são filtrados por sellerId em toda query — não existe chamada ao banco que não passe por esse filtro. Isso garante isolamento total entre vendedores.

Resultados em 2 meses

  • R$ 30k+ em faturamento processado pela plataforma
  • 100+ usuários ativos
  • 400+ pedidos processados
  • MRR de R$ 20k — validando product-market fit
  • 2 marketplaces integrados (Mercado Livre + Shopee)
  • 2 gateways de pagamento (Asaas + Mercado Pago)
  • Zero downtime desde o lançamento

Lições aprendidas

  1. Adapter pattern é essencial quando você integra com sistemas externos que mudam sem aviso. Investir tempo na abstração no início economizou semanas depois
  2. Idempotência não é opcional — com webhooks de pagamento, é a diferença entre cobrar o cliente uma vez ou duas vezes
  3. Observabilidade desde o dia 1. Quando o Mercado Livre mudou o formato de um campo sem documentar, nossos logs estruturados mostraram exatamente qual campo quebrou, em qual seller, em qual pedido. Sem isso, seriam horas de debug
  4. Escala não é só infra. Os primeiros gargalos foram queries mal indexadas e pool de conexão mal configurado — problemas de aplicação, não de servidor

Stack final

  • React + Next.js + TypeScript (frontend)
  • NestJS (API backend)
  • PostgreSQL + Firebase (banco e auth)
  • Docker + AWS (infraestrutura)
  • Asaas + Mercado Pago (pagamentos)
  • Mercado Livre API + Shopee API (marketplaces)
  • CI/CD com deploy automatizado

--- Quer conhecer mais sobre o projeto, o propósito e ver a plataforma? Acesse a página dedicada da X-Drop.