Política de retentativa¶
A Gubee entrega webhooks com semântica at-least-once: cada evento é tentado até 5 vezes e, se ainda assim falhar, vai para uma fila persistente de notificações perdidas que você pode recuperar por API.
Ciclo de vida de um evento¶
- Evento de negócio acontece na Gubee (pedido faturado, estoque alterado, etc.).
- A Gubee registra o evento com timestamp
receivede o coloca na fila de saída. - Primeira tentativa (
attempts: 1):POSTno endpoint cadastrado. - Se resposta
2xx: evento marcado como entregue, fim. - Se resposta não-
2xx, timeout ou erro de rede: agenda nova tentativa com backoff exponencial. - Após 5 tentativas sem sucesso, o evento é movido para a fila de notificações perdidas (
missed), onde fica disponível para consulta por API. - Você lê a fila periodicamente, reprocessa e marca como lido (
DELETE).
Backoff exponencial¶
A Gubee aplica backoff exponencial entre tentativas (lado servidor). Os intervalos exatos são ajustáveis e não fazem parte do contrato público, mas a política é:
- Não há retentativa imediata — sempre há um intervalo mínimo.
- O intervalo cresce a cada nova tentativa, tipicamente da ordem de minutos, depois dezenas de minutos, depois horas.
- A janela total até um evento ir para
missedcostuma ficar em algumas horas.
Como receptor, isso significa que:
- Se o seu endpoint cair por 1 minuto, você provavelmente vai receber por retentativa.
- Se cair por horas, parte dos eventos vai parar em
missed— planeje consumir essa fila.
Fila de notificações perdidas¶
Endpoint: GET /integration/notifications/missed/{urlName}
O {urlName} é o nome que você deu ao endpoint ao cadastrá-lo no painel Gubee (Minha Conta → Notificações de Eventos). Não é a URL; é o rótulo. Se você não lembra, consulte o painel.
A resposta é paginada em HAL+JSON (PagedModel<EntityModel<MissedNotificationApiDTO>>):
{
"_embedded": {
"missedNotificationApiDTOList": [
{
"id": "60d5ecb8b3d4c20001a1b2c3",
"resource": "/integration/orders/1234567890",
"sellerId": "5f3e2a1b2c3d4e5f6a7b8c9d",
"domain": "order",
"endpointId": "erp-prod-001",
"type": "OrderInvoiced",
"attempts": 5,
"lastAttemptDateTime": "2022-06-15T17:55:12.000000Z",
"request": {
"url": "https://erp.seudominio.com.br/gubee/webhook",
"body": "{\"resource\":\"/integration/orders/1234567890\",...}"
},
"response": {
"httpStatus": 504,
"body": "Gateway Timeout"
}
}
]
},
"page": {
"size": 20,
"totalElements": 1,
"totalPages": 1,
"number": 0
},
"_links": {
"self": { "href": "https://api.gubee.com.br/integration/notifications/missed/erp-prod-001?page=0&size=20" }
}
}
Campos da notificação perdida¶
| Campo | Descrição |
|---|---|
id |
Identificador da notificação na fila missed. Use no DELETE para marcar como lida. |
resource, sellerId, domain, type |
Mesmo significado do payload de webhook. |
endpointId |
O urlName que você cadastrou. |
attempts |
Total de tentativas (sempre 5 quando chega aqui). |
lastAttemptDateTime |
Timestamp da última tentativa falha. |
request.url |
URL que a Gubee tentou chamar. |
request.body |
Payload bruto enviado — útil para auditoria e replay. |
response.httpStatus |
Status HTTP devolvido pelo seu endpoint na última tentativa (0 se timeout/connrefused). |
response.body |
Corpo da resposta do seu endpoint, se houver. |
Os campos request e response estão presentes apenas em chamadas que de fato aconteceram (se a Gubee nunca conseguiu abrir conexão, podem estar ausentes).
Fluxo de recuperação completo¶
TOKEN="<seu jwt>"
URL_NAME="erp-prod-001"
BASE="https://api.gubee.com.br"
# 1. Lista as primeiras 50 perdidas
curl -sS \
-H "Authorization: Bearer $TOKEN" \
-H 'Accept: application/hal+json' \
"$BASE/integration/notifications/missed/$URL_NAME?size=50" \
| jq '._embedded.missedNotificationApiDTOList[] | {id, type, resource, response: .response.httpStatus}'
Saída típica:
{
"id": "60d5ecb8b3d4c20001a1b2c3",
"type": "OrderInvoiced",
"resource": "/integration/orders/1234567890",
"response": 504
}
Para cada item retornado:
- Reprocesse — use o
resourcepara buscar a entidade atualizada e execute sua lógica de negócio. - Aplique idempotência — o mesmo evento pode ter sido entregue com sucesso em uma tentativa anterior e ainda assim ter chegado à fila por inconsistência. Veja idempotency.md.
- Marque como lida com
DELETE:
NOTIFICATION_ID="60d5ecb8b3d4c20001a1b2c3"
curl -sS -X DELETE \
-H "Authorization: Bearer $TOKEN" \
"$BASE/integration/notifications/missed/$NOTIFICATION_ID" \
-w "%{http_code}\n"
Resposta esperada: 204 No Content. A notificação some da próxima paginação.
Script de drenagem¶
Exemplo de loop que consome todas as páginas até esvaziar a fila:
#!/usr/bin/env bash
set -euo pipefail
TOKEN="<seu jwt>"
URL_NAME="erp-prod-001"
BASE="https://api.gubee.com.br"
page=0
while :; do
resp=$(curl -sS \
-H "Authorization: Bearer $TOKEN" \
-H 'Accept: application/hal+json' \
"$BASE/integration/notifications/missed/$URL_NAME?size=50&page=$page")
ids=$(echo "$resp" | jq -r '._embedded.missedNotificationApiDTOList[].id // empty')
total=$(echo "$resp" | jq -r '.page.totalElements')
[[ -z "$ids" ]] && { echo "fila vazia"; break; }
while read -r nid; do
echo "reprocessando $nid..."
# ... sua lógica aqui (chamar o resource, gravar no ERP, etc.) ...
curl -sS -X DELETE \
-H "Authorization: Bearer $TOKEN" \
"$BASE/integration/notifications/missed/$nid" -o /dev/null -w "ack $nid: %{http_code}\n"
done <<< "$ids"
((page++))
echo "total restante (antes do ack): $total"
done
Retenção¶
A fila missed tem retenção longa (configurável internamente). Trate-a como uma fila operacional, não como armazenamento permanente — faça a drenagem periódica (ex.: a cada hora) e nunca conte com eventos antigos para reconciliação histórica. Para histórico de pedidos, use diretamente GET /integration/orders.
Boas práticas¶
- Monitore a fila. Se ela crescer, seu endpoint está instável. Alerta se
totalElements > threshold. - Implemente replay por tabela interna. Salve todo webhook recebido numa tabela
webhook_inboxcom estado (PENDING,PROCESSED,FAILED). Quando precisar refazer, dê replay local, não dependa da fila da Gubee. - Tenha sua própria chave de dedup. A entrega é at-least-once (veja idempotency.md). Sugestão de chave:
hash(itemId + type + received). - Não processe síncrono no handler HTTP. Salve o payload e responda
2xxrápido. O processamento pesado vai num worker assíncrono. - Combine com a fila de pedidos. A fila
/integration/orders/queue/{status}é complementar: ela contém os pedidos por status, independente de webhook ter sido disparado ou não. Útil como reconciliação diária.
Fila de pedidos como alternativa ao webhook¶
Se você não quer (ou não pode) expor um endpoint público, pode usar a fila de pedidos como única via:
# Lista pedidos prontos para faturar
curl -sS \
-H "Authorization: Bearer $TOKEN" \
"$BASE/integration/orders/queue/invoiced?size=50"
# Ack após processar
curl -sS -X DELETE \
-H "Authorization: Bearer $TOKEN" \
"$BASE/integration/orders/queue/invoiced/1234567890"
Status suportados na fila: created, canceled, paid/payed, invoiced, shipped, delivered, rejected. Os pedidos reaparecem na fila até que você faça o DELETE de ack — então a semântica é at-least-once também aqui. Há ainda um endpoint dedicado /integration/orders/queue/rejected com retenção de 30 dias para pedidos cuja atualização de canceled/shipped/delivered foi rejeitada pelo marketplace.
Veja mais detalhes no guia de jornada Receber pedidos no ERP.
Próximos passos¶
- Idempotência — como modelar a chave de dedup que vale para webhook e fila.
- Eventos — catálogo completo.
- Schema do payload — referência dos campos.