Pular para conteúdo

Eventos de Webhook

Webhooks são notificações HTTP POST enviadas pela Gubee para um endpoint seu sempre que um evento de negócio acontece (pedido criado, estoque alterado, preço atualizado, etc.). Eles eliminam a necessidade de polling e reduzem a latência entre o fato no marketplace e o processamento no seu ERP.

Quando usar webhook vs. polling

Use webhook como via primária de integração: ele é push, em tempo real e dispara apenas quando há mudança. Use polling (fila de pedidos) apenas como fallback ou reconciliação — quando o seu endpoint ficar fora do ar, quando você perder um evento ou quando quiser garantir consistência eventual.

A Gubee mantém uma fila de pedidos consultável em GET /integration/orders/queue/{status} projetada exatamente para esse cenário de recuperação. Veja o detalhamento no guia de jornada Receber pedidos no ERP.

Critério Webhook Fila (polling)
Direção Push (Gubee → você) Pull (você → Gubee)
Latência Segundos Definida pelo seu scheduler
Esforço de implementação Médio (endpoint público, idempotência) Baixo (job que consome fila)
Recomendado para Produção, tempo real Fallback, reprocessamento, teste

Em produção madura, use os dois: webhook como via principal e um job periódico lendo a fila para reconciliar eventuais falhas.

Como assinar eventos

A configuração de webhooks é feita no painel administrativo da Gubee, não por API:

  1. Acesse o painel Gubee com usuário administrador.
  2. Vá em Minha Conta → Notificações de Eventos.
  3. Cadastre a URL do endpoint que vai receber os POSTs. A URL precisa ser pública, aceitar HTTPS e responder 2xx em até 5 segundos.
  4. Dê um nome (urlName) para esse endpoint — você vai usar esse nome para recuperar notificações perdidas (veja retry-policy.md).
  5. Marque os status que você quer receber (assinatura por status, não por domínio inteiro).

Dica para ERPs de marketplace

ERPs que só cuidam do fluxo de venda não precisam assinar todos os status. Para o ciclo clássico de pedido, assine apenas:

  • Faturado — dispara a emissão da NF-e no seu ERP.
  • Enviado — dispara o rastreio/logística reversa.
  • Entregue — fecha o ciclo e habilita pós-venda.
  • Cancelado — apenas se o seu hub puder cancelar/estornar; caso contrário, ignore.

Status como Criado e Pago são úteis para reservas de estoque antecipadas, mas na maioria dos fluxos o Faturado já é o gatilho suficiente. Quanto menos eventos você assinar, menos ruído e menos deduplicação no seu lado.

Catálogo de eventos

Os eventos se dividem em quatro domínios (domain). O campo itemId muda de significado conforme o domínio.

Domain type Gatilho itemId significa
order OrderCreated Novo pedido criado na Gubee orderId
order OrderPayed Pedido marcado como pago orderId
order OrderInvoiced Nota fiscal anexada ao pedido orderId
order OrderShipped Pedido marcado como enviado orderId
order OrderDelivered Pedido marcado como entregue orderId
order OrderCanceled Pedido cancelado orderId
order OrderReturned Pedido devolvido orderId
order OrderShipmentException Exceção de entrega (cliente não encontrado, endereço inválido, etc.) orderId
product VariationCreated Nova variação criada skuId
product VariationUpdated Variação atualizada skuId
price PriceUpdated Preço alterado ID interno de preço (use relatedId para o skuId)
stock StockUpdated Estoque alterado ID interno de estoque (use relatedId para o skuId)

Status disponíveis para assinatura no painel: Criado, Pago, Faturado, Enviado, Entregue, Concluído, Retornado, Cancelado, Exceção na entrega.

Para price e stock, o itemId é um identificador interno da Gubee — não tente usá-lo diretamente. O skuId relevante vem no campo relatedId.

Exemplos de payload por domínio

Pedido (order)

{
  "resource": "/integration/orders/1234567890",
  "domain": "order",
  "sellerId": "5f3e2a1b2c3d4e5f6a7b8c9d",
  "itemId": "1234567890",
  "type": "OrderInvoiced",
  "attempts": 1,
  "sent": "2022-06-15T17:14:50.432503Z",
  "received": "2022-06-15T17:13:07.555760Z"
}

Produto (product)

{
  "resource": "/integration/products/variations/sku-ABC123",
  "domain": "product",
  "sellerId": "5f3e2a1b2c3d4e5f6a7b8c9d",
  "itemId": "sku-ABC123",
  "type": "VariationUpdated",
  "attempts": 1,
  "sent": "2022-06-15T17:14:50.432503Z",
  "received": "2022-06-15T17:13:07.555760Z"
}

Preço (price)

{
  "resource": "/integration/prices/654321",
  "domain": "price",
  "sellerId": "5f3e2a1b2c3d4e5f6a7b8c9d",
  "itemId": "654321",
  "relatedId": "sku-ABC123",
  "type": "PriceUpdated",
  "attempts": 1,
  "sent": "2022-06-15T17:14:50.432503Z",
  "received": "2022-06-15T17:13:07.555760Z"
}

Estoque (stock)

{
  "resource": "/integration/stock/987654",
  "domain": "stock",
  "sellerId": "5f3e2a1b2c3d4e5f6a7b8c9d",
  "itemId": "987654",
  "relatedId": "sku-ABC123",
  "type": "StockUpdated",
  "attempts": 1,
  "sent": "2022-06-15T17:14:50.432503Z",
  "received": "2022-06-15T17:13:07.555760Z"
}

O campo resource é o caminho absoluto na API de integração que devolve a entidade completa. Use-o para buscar o estado atualizado logo ao receber o webhook:

curl -sS -H 'Authorization: Bearer $TOKEN' "https://api.gubee.com.br${RESOURCE}" | jq .

Veja a referência completa dos campos em payload-schema.md.

Filtro de contexto é responsabilidade de quem recebe

Importante: a Gubee dispara eventos para todos os itens relevantes ao sellerId, mesmo quando o item não pertence ao contexto do marketplace que o receptor integra. Por exemplo: se você tem uma integração exclusiva com Mercado Livre, vai receber eventos de pedidos do SHopee, Magalu, Amazon, etc., mesmo que não vá processá-los.

Isso é por design — a Gubee não conhece o mapeamento "este webhook = este marketplace". O receptor deve filtrar por contexto. A forma usual é consultar o pedido/produto via resource e inspecionar o campo de canal.

Exemplo de handler que filtra por contexto (pseudo-Kotlin):

@PostMapping("/gubee/webhook")
fun handle(@RequestBody event: WebhookEvent): ResponseEntity<Void> {
    // 1. Busca a entidade completa usando o campo resource
    val entity = gubeeClient.fetch(event.resource)

    // 2. FILTRO DE CONTEXTO: ignora pedidos de marketplaces que você não integra
    if (event.domain == "order") {
        val order = entity as Order
        if (order.channel ! in SUPPORTED_CHANNELS) {
            // Não é seu contexto — apenas acuse recebimento e abandone
            return ResponseEntity.ok().build()
        }
    }

    // 3. Idempotência (ver idempotency.md)
    val dedupKey = "${event.itemId}|${event.type}|${event.received}"
    if (dedupRepository.existsByKey(dedupKey)) {
        return ResponseEntity.ok().build()
    }
    dedupRepository.save(DedupEntry(key = dedupKey, receivedAt = Instant.now()))

    // 4. Processamento de fato
    when (event.type) {
        "OrderInvoiced" -> invoiceService.sync(order)
        "OrderShipped"  -> shippingService.dispatch(order)
        "OrderCanceled" -> orderService.cancel(order)
        // ...
    }

    return ResponseEntity.ok().build()
}

Sem esse filtro, você vai processar (e possivelmente duplicar no ERP) pedidos que não são da sua alçada. Sempre acuse 2xx mesmo para eventos ignorados — caso contrário a Gubee vai retentar e poluir sua fila de perdidas.

Próximos passos