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:
- Índices compostos nas tabelas de pedidos e transações — queries de relatório voltaram para < 100ms
- Connection pooling com limites separados para operações síncronas (API) e assíncronas (sync de marketplace)
- Rate limiting por seller nas chamadas aos marketplaces — evita que um vendedor com catálogo grande consuma toda a cota da API
- 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
- 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
- Adapter pattern é essencial quando você integra com sistemas externos que mudam sem aviso. Investir tempo na abstração no início economizou semanas depois
- Idempotência não é opcional — com webhooks de pagamento, é a diferença entre cobrar o cliente uma vez ou duas vezes
- 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
- 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.
