Registrar o webhook
O webhook é o canal automatizado pelo qual você descobre o resultado do
processamento assíncrono: sem ele, um lote aceito com 200 OK que falhou depois não
avisa o seu ERP.
Ele não é a única forma de enxergar o que aconteceu — o painel administrativo da Grocers mostra as operações e o resultado de cada uma, e serve para conferência manual e apuração de suporte. A diferença é o uso: o painel é consulta humana, o webhook é o que a sua integração consome sem alguém olhando. Para operação diária, é o webhook que precisa estar de pé.
Endpoint
POST /v1/Webhookscurl -X POST $GROCERS_API/v1/Webhooks \
-H "Authorization: Basic $BASIC" \
-H "x-contractAccountId: $TENANT" \
-H "Content-Type: application/json" \
-d '{
"originType": "Api_call",
"originData": "ERP-Producao",
"url": "https://api.seusite.com/webhooks/grocers",
"authType": "Bearer",
"authToken": "s3gr3d0-do-erp",
"eventTypes": [
"ProductWithSKUs_Created",
"ProductWithSKUs_Updated",
"ProductWithSKUs_CreationFailed",
"ProductWithSKUs_UpdateFailed",
"ProductWithSKUs_PartiallyFailed",
"Order_Updated",
"Order_StatusUpdateFailed",
"Order_CancelFailed",
"Operation_Failed"
]
}'Resposta de sucesso — 200, com dois campos e nada mais:
{
"id": "5c1d9e70-2b3a-4f18-9d6c-7e8f0a1b2c3d",
"url": "https://api.seusite.com/webhooks/grocers"
}| Código | Significado | O que fazer |
|---|---|---|
200 | registro criado | guarde o id; ele é o único comprovante que você terá |
400 | originType fora do enum, originData ou url vazios, um eventTypes inválido, ou x-contractAccountId ausente/malformado | corrija o corpo ou o header |
409 | já existe um webhook para este contractAccountId | não há rota de alteração nem de exclusão: abra chamado |
500 | falha interna | retente |
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
originType | enum | sim | User (1) ou Api_call (2) |
originData | string | sim | Identificador livre da origem |
url | string | sim | URL que recebe as notificações — só é validado que não está vazia |
authType | string | não | Bearer ou Basic |
authToken | string | não | Valor enviado no header Authorization de cada entrega |
eventTypes | array | na prática, sim | Eventos assinados — veja Eventos de webhook |
Formato da notificação
Toda notificação chega como POST com o mesmo envelope, qualquer que seja o evento:
{
"operationId": "54d6a979-5f62-4e17-9d4c-dbd9f5ede403",
"contractAccountId": "9f36a666-acd5-4987-a47f-3de247f65d82",
"relatedEntity": "ProductWithSKUs",
"eventType": "ProductWithSKUs_Created",
"data": {
"product": {
"externalReference": "PROD-114970",
"name": "Macarrão Espaguete 500g",
"processed": true,
"error": null
},
"skus": {
"total": 2,
"successful": 2,
"failed": 0,
"successfulSKUs": ["114970", "114970-CS"],
"failedSKUs": []
},
"summary": {
"status": "Success",
"processedAt": "2026-07-29T14:32:10.512Z"
}
}
}O data de catálogo é um relatório do processamento do produto inteiro, não uma cópia
do que você enviou: um produto e seus SKUs viram uma notificação só, com a contagem de
sucessos e a lista dos SKUs que falharam. summary.status é Success, PartialFailure
ou Failed.
| Campo | Descrição |
|---|---|
operationId | Correlaciona a notificação com o lote que a originou |
relatedEntity | Entidade do evento — um de ProductWithSKUs, Category, Brand, Warehouse, WarehouseSKU, SKUPricing, Order, User |
eventType | Nome do evento, em texto — não o código numérico |
data | Payload específico do evento |
contractAccountId | Tenant dono do dado |
error | Presente apenas em falhas |
Onde fica o erro
O diagnóstico de falha aparece em um de dois lugares, e qual deles depende da entidade:
| Entidade | Onde está o erro |
|---|---|
ProductWithSKUs | dentro de data — em data.product.error e em data.skus.failedSKUs[].error. O campo error do envelope não vem |
Brand, Category, Warehouse, SKUPricing, Order, User | no campo error do envelope |
Catálogo (ProductWithSKUs_CreationFailed, _UpdateFailed, _PartiallyFailed):
{
"operationId": "54d6a979-5f62-4e17-9d4c-dbd9f5ede403",
"contractAccountId": "9f36a666-acd5-4987-a47f-3de247f65d82",
"relatedEntity": "ProductWithSKUs",
"eventType": "ProductWithSKUs_PartiallyFailed",
"data": {
"product": {
"externalReference": "PROD-114970",
"name": "Macarrão Espaguete 500g",
"processed": true,
"error": null
},
"skus": {
"total": 2,
"successful": 1,
"failed": 1,
"successfulSKUs": ["114970"],
"failedSKUs": [
{
"externalReference": "114970-CS",
"error": {
"statusCode": 400,
"message": "{\"errors\":{\"ManufacturerCode\":[\"Código do fabricante é obrigatório\"]}}",
"title": "Requisição inválida",
"detail": "Ocorreram um ou mais erros de validação",
"validationErrors": [
{ "id": 1, "key": "ManufacturerCode", "validation": "Código do fabricante é obrigatório" }
]
}
}
]
},
"summary": {
"status": "PartialFailure",
"processedAt": "2026-07-29T14:32:10.512Z"
}
}
}Demais entidades — o erro vem no envelope:
{
"operationId": "54d6a979-5f62-4e17-9d4c-dbd9f5ede403",
"contractAccountId": "9f36a666-acd5-4987-a47f-3de247f65d82",
"relatedEntity": "Brand",
"eventType": "Brand_CreationFailed",
"data": { "externalReference": "MARCA-001", "name": "Marca Teste" },
"error": {
"statusCode": 400,
"message": "{\"errors\":{\"Name\":[\"Nome é um campo obrigatório\"]}}",
"title": "Requisição inválida",
"detail": "Ocorreram um ou mais erros de validação",
"instance": "/v1/brands/integration",
"traceId": "00-3f2a91c4b7…-01",
"validationErrors": [
{ "id": 1, "key": "Name", "validation": "Nome é um campo obrigatório" }
]
}
}Campo de error | Conteúdo |
|---|---|
statusCode | código HTTP que o core devolveu ao processador |
message | corpo cru da resposta do core — guarde-o, é o que sobra quando validationErrors vem vazio |
title / detail / instance / traceId | campos do ProblemDetails, quando o core respondeu nesse formato |
validationErrors | lista { id, key, validation } — key é a propriedade reprovada |
Como receber bem
Persista, responda, processe depois
Grave a notificação crua numa fila sua, responda 200 e só então processe. Nessa
ordem: se você responde antes de persistir e o seu processo cai no meio, a plataforma
já considerou a entrega bem-sucedida e o evento não volta.
Trate como “pelo menos uma vez”
A retentativa pode entregar a mesma notificação mais de uma vez — são até 3 entregas, sem espera entre elas.
A chave de deduplicação canônica, válida para todo tipo de evento, é
operationId + eventType + a identidade da entidade dentro de data. Ela está
definida com a tabela por entidade em
Receber pedidos (webhook) → Deduplicação.
Não deduplique só por operationId. Ele identifica a operação, não a notificação:
um POST /v1/batch/Products com 500 produtos gera 500 notificações com o mesmo
operationId. Deduplicar por ele descarta 499.
Não assuma ordem
Notificações de um mesmo lote chegam independentes entre si. Se a ordem importa para o seu processamento, reordene pelo seu lado.
Registre o payload cru
Guardar o JSON recebido, mesmo o que você descartou, é o que torna possível investigar divergência depois.