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
2xximediatamente 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:
- No handler HTTP: valide a assinatura, persista o payload bruto numa tabela/fila local e responda
200imediatamente. - 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¶
- Catálogo de eventos — todos os
typepor domínio. - Política de retentativa — o que acontece quando você não responde
2xx. - Idempotência — por que
attempts > 1pode chegar duplicado mesmo com sucesso.