Webhooks de Rastreio
Receba cada mudança de status dos seus rastreios via HTTP POST, com assinatura HMAC, retry automático e auto-disable em falhas consecutivas.
Visão Geral
Webhooks disparam sempre que o último evento de um rastreio mudar, tanto para rastreios avulsos como para pedidos integrados (Lojaverso, Nuvemshop etc.). Você cadastra endpoints HTTPs e escolhe quais tipos de evento cada endpoint assina.
Um evento pode gerar zero, um ou vários POSTs — um para cada endpoint elegível. As entregas são enfileiradas de forma durável, entregues por workers em background e reentregues com backoff em caso de falha (não bloqueiam o processamento da fila de rastreio).
Autenticação (gerenciamento via API)
Para cadastrar, listar e excluir webhooks via API, use o header Authorization: Bearer sr_live_.... Cada chave pertence a um time e só enxerga os webhooks do próprio time.
Authorization: Bearer sr_live_sua_chave_aquiTipos de evento
Ao criar um webhook você informa a lista de eventos assinados. Use default como catch-all para receber qualquer tipo.
| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
em_curso | evento | não | Objeto em trânsito (qualquer evento não-terminal). |
saiu_para_entrega | evento | não | Saiu para entrega ao destinatário. |
entregue | evento | não | Entrega confirmada pelo rastreio. |
nao_postado | evento | não | Objeto postado (primeiro registro na transportadora). |
default | evento | não | Catch-all — recebe qualquer evento mesmo se os outros não estiverem na lista. |
Payload entregue
O corpo da requisição é JSON com o evento, informações do time, do rastreio e opcionalmente do pedido vinculado.
{
"id": "d1e5b8a3-...",
"event": "saiu_para_entrega",
"eventLabel": "Saiu para entrega",
"occurredAt": "2026-04-21T14:32:01.512Z",
"team": { "id": "team_abc", "slug": "minha-loja" },
"tracking": {
"codigo": "AA123456789BR",
"tipo": "normal",
"lastEventDescription": "Objeto saiu para entrega ao destinatário",
"lastEventLocation": "São Paulo/SP",
"lastEventAt": "2026-04-21T14:30:00.000Z"
},
"order": {
"orderId": "livexa_12345",
"status": "shipped",
"operationalStatus": "saiu_para_entrega"
}
}O campo order só está presente quando o evento foi disparado por uma atualização de pedido integrado.
Headers enviados
| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
X-SeuRastreio-Event | string | sim | Tipo de evento (ex.: em_curso, entregue). |
X-SeuRastreio-Delivery | string | sim | Identificador único da tentativa de entrega (idempotência). |
X-SeuRastreio-Timestamp | string | sim | Unix timestamp (segundos) usado no cálculo do HMAC. |
X-SeuRastreio-Signature | string | sim | Assinatura HMAC-SHA256 no formato t=<unix_ts>,v1=<hex>. |
Verificando a assinatura (HMAC-SHA256)
Compute HMAC-SHA256(secret, `$`{timestamp}.{rawBody}$``) e compare com o valor de v1 em X-SeuRastreio-Signature. Use comparação em tempo constante (ex.: crypto.timingSafeEqual no Node).
import crypto from "node:crypto";
export function verifySeuRastreioSignature(
secret: string,
rawBody: string,
signatureHeader: string,
): boolean {
const parts = Object.fromEntries(
signatureHeader.split(",").map((p) => p.split("=") as [string, string]),
);
const ts = Number(parts.t);
const v1 = parts.v1;
if (!ts || !v1) return false;
// Rejeita requisições com mais de 5 minutos para mitigar replay.
if (Math.abs(Date.now() / 1000 - ts) > 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${ts}.${rawBody}`)
.digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(v1);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}X-SeuRastreio-Delivery como chave de idempotência no seu lado. O mesmo delivery pode ser re-tentado várias vezes em caso de timeout — processe uma vez só.Política de retry & auto-disable
Uma resposta HTTP 2xx conta como sucesso. Qualquer outro status, timeout (10s) ou erro de rede agenda nova tentativa com backoff crescente:
| Status | Significado |
|---|---|
1 | Após 1 minuto |
2 | Após 5 minutos |
3 | Após 30 minutos |
4 | Após 2 horas |
5 | Após 6 horas — se falhar, delivery marcado como failed |
Após 10 falhas consecutivas no mesmo endpoint, ele é desativado automaticamente (enabled = false). Reative pela dashboard ou enviando PATCH com enabled: true.
Endpoints REST para gerenciar webhooks
Listar webhooks do time
/api/public/webhookscurl -sS "https://seurastreio.com.br/api/public/webhooks" \
-H "Authorization: Bearer sr_live_sua_chave"Criar webhook
/api/public/webhooks| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
name | string | sim | Nome livre (máx. 120 caracteres). |
url | string | sim | URL HTTPS (HTTP permitido em dev). |
events | string[] | sim | Lista de eventos a assinar (ex.: ["em_curso","entregue"]). |
curl -sS -X POST "https://seurastreio.com.br/api/public/webhooks" \
-H "Authorization: Bearer sr_live_sua_chave" \
-H "Content-Type: application/json" \
-d '{
"name": "Integração ERP",
"url": "https://meu-erp.com/webhooks/seurastreio",
"events": ["em_curso","saiu_para_entrega","entregue"]
}'A resposta (HTTP 201) inclui o campo secret em claro — guarde agora, pois ele não é exibido em chamadas posteriores.
Consultar webhook
/api/public/webhooks/{id}Atualizar webhook
/api/public/webhooks/{id}Aceita qualquer combinação de name, url, events e enabled. Enviar {"enabled": true} reativa um endpoint que tenha sido desabilitado automaticamente.
Excluir webhook
/api/public/webhooks/{id}Rotacionar o secret
/api/public/webhooks/{id}/rotate-secretGera um novo secret e invalida o anterior. A resposta expõe o novo secret em claro.
Disparar um evento de teste
/api/public/webhooks/{id}/testEnfileira um delivery sintético (event=default e _test: true no payload) para validar conectividade e assinatura.
Listar entregas recentes
/api/public/webhooks/{id}/deliveriesQuery opcional limit (default 50, máx 200). Retorna status, número de tentativas, último erro e nextAttemptAt.
Códigos de status (API de gerenciamento)
| Status | Significado |
|---|---|
200 | Operação bem-sucedida |
201 | Webhook criado |
202 | Delivery de teste enfileirado |
400 | Validação falhou (payload inválido) |
401 | Chave ausente ou inválida |
404 | Webhook não encontrado para o time |
500 | Erro interno |