Pular para conteúdo

Schema do payload

Todo evento de webhook é um POST com Content-Type: application/json cujo corpo segue o schema abaixo. O schema é o mesmo para todos os domínios (order, product, price, stock) — o que muda é o significado de itemId (veja events.md).

Exemplo completo

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

Referência de campos

Campo Tipo Obrigatório Descrição
resource string sim Caminho absoluto na API de integração que devolve a entidade completa referente ao evento (ex.: /integration/orders/{orderId}).
domain enum sim Domínio do evento. Um de: order, product, price, stock.
sellerId string sim Identificador do vendedor na Gubee (token decodificado).
itemId string sim Identificador primário do recurso. Significado varia por domain: orderId para pedidos, skuId para produtos, ID interno para preço/estoque.
type string sim Tipo do evento. Catálogo completo em events.md.
relatedId string não Presente apenas em eventos de price e stock. Contém o skuId do produto cujo preço/estoque mudou.
attempts int sim Número da tentativa de entrega deste evento. Varia de 1 (primeira) a 5 (última antes de ir para a fila de perdidas).
sent ISO 8601 sim Momento em que a última tentativa foi enviada pela Gubee. Fuso UTC, formato yyyy-MM-dd'T'HH:mm:ss.SSSSSSZ.
received ISO 8601 sim Momento em que a Gubee gerou o evento internamente (antes de qualquer entrega).

Sobre sent vs. received

  • received é o marco temporal do evento de negócio (quando o fato aconteceu na Gubee).
  • sent é quando essa tentativa específica foi despachada para o seu endpoint.

A diferença sent - received indica a latência acumulada de retentativas. Se você vir attempts > 1 e sent - received da ordem de minutos ou horas, é sinal de que seu endpoint está devagar ou devolvendo não-2xx. Considere:

  • Aumentar a paralelização do consumidor.
  • Descarregar o processamento para uma fila interna (uma fila interna (SNS/SQS, RabbitMQ, Redis Streams, etc)) e responder 2xx imediatamente após enfileirar.

Veja detalhes em retry-policy.md.

Como acusar recebimento

A Gubee considera o evento entregue quando o seu endpoint responde com status HTTP 2xx (qualquer um: 200, 201, 204, etc.). Qualquer outra faixa (3xx, 4xx, 5xx) ou timeout conta como falha e dispara a próxima tentativa.

O timeout sugerido é de 5 segundos. Se o seu processamento de negócio leva mais que isso (gravar em banco, chamar nota fiscal, sincronizar com outro serviço), não faça síncrono:

  1. No handler HTTP: valide a assinatura, persista o payload bruto numa tabela/fila local e responda 200 imediatamente.
  2. Em um worker assíncrono: leia a fila local, faça o processamento pesado, atualize o estado.

Esse padrão protege você de timeouts da Gubee e dá replay nativo em caso de crash do worker.

Exemplo: ack síncrono mínimo (Express)

app.post('/gubee/webhook', express.json(), async (req, res) => {
  const event = req.body;

  // Persiste o payload bruto com estado PENDING — não processa aqui.
  await db.query(
    'INSERT INTO webhook_inbox (dedup_key, payload, status) VALUES ($1, $2, $3)',
    [`${event.itemId}|${event.type}|${event.received}`, JSON.stringify(event), 'PENDING']
  ).catch(err => {
    // Dedup key duplicada -> 23505 no Postgres -> já recebemos antes, tudo certo.
    if (err.code !== '23505') throw err;
  });

  // 2xx imediato — o worker vai pegar depois.
  res.status(200).end();
});

Como buscar a entidade completa

O payload do webhook é uma notificação, não traz o estado da entidade. Para obter os dados do pedido/produto/preço/estoque, faça um GET no caminho indicado por resource:

RESOURCE="/integration/orders/1234567890"

curl -sS \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'Accept: application/hal+json' \
  "https://api.gubee.com.br${RESOURCE}" | jq .

Sempre use o valor de resource exatamente como veio — não tente recompô-lo concatenando domínio e itemId, porque os caminhos variam (pedidos usam orderId, variações usam skuId, preço/estoque usam IDs internos).

Resposta para pedido (HAL+JSON)

{
  "orderId": "1234567890",
  "status": "INVOICED",
  "channel": "MERCADOLIVRE",
  "customer": { "name": "João da Silva", "email": "joao@example.com" },
  "items": [
    { "skuId": "sku-ABC123", "quantity": 2, "price": 89.90 }
  ],
  "total": 179.80,
  "_links": {
    "self":   { "href": "https://api.gubee.com.br/integration/orders/1234567890" },
    "invoice": { "href": "https://api.gubee.com.br/integration/orders/1234567890/invoice" }
  }
}

A maior parte das respostas de pedidos/produtos/categorias/marcas retorna em HAL+JSON (application/hal+json), com links HATEOAS em _links. Endpoints V2 de estoque, preço e vídeo retornam JSON plano (sem _links). Veja mais em ../reference/README.md.

Content-Type e encoding

  • Content-Type: application/json (o webhook enviado ao seu endpoint).
  • Encoding UTF-8.
  • Datas em ISO 8601 com sufixo Z (UTC). Não confie em fuso local.

Segurança do endpoint

A Gubee não assina os payloads de webhook hoje. Recomendações:

  • Exponha o endpoint apenas sobre HTTPS.
  • Use um token opaco na query string ou header custom (ex.: X-Gubee-Token) configurado por você no painel — a Gubee envia o que você cadastra.
  • Não confie no IP de origem (pode mudar); valide sempre o token.
  • Se você replica o payload para outro serviço interno, preserve o corpo bruto para auditoria.

Próximos passos