Webhooks v2 entregam eventos de pedido e rastreamento em endpoints configurados em Ferramentas → Integrações → Webhooks. Cada endpoint pertence ao produtor autenticado e assina os eventos escolhidos no painel.
O painel não oferece filtro de produtos por endpoint: todo endpoint ativo recebe os eventos assinados de todos os pedidos do produtor. O filtro por produto existe apenas no formulário de reenvio em lote, descrito no final desta página.
Requisição HTTP
Cada evento é enviado por POST, com JSON e timeout de resposta de 30 segundos.
| Header | Presença | Valor |
|---|---|---|
Content-Type |
Sempre | application/json |
X-Webhook-Event |
Sempre | Código exato do evento, por exemplo shipment.delivered. |
X-Webhook-Signature |
Opcional | sha256=<hex>; omitido somente quando o endpoint não possui segredo. Endpoints criados pelo painel recebem um segredo. |
X-Webhook-Test |
Condicional: somente teste do painel | true |
Eventos disponíveis
Pedido
| Evento | Rótulo | Disparo real |
|---|---|---|
order.created |
Pedido Criado | Após a criação do pedido, exceto filho agregado/upsell com status Agregado. |
order.label_ready |
Etiqueta Pronta | Ao receber RastreioRecebido. Reemissões do mesmo pedido são deduplicadas por 7 dias. |
order.preparing |
Em Preparação | Quando status_code_id muda para Preparação. |
order.blocked |
Parado | Quando status_code_id muda para Parado. |
order.refunded |
Estornado | Quando status_code_id muda para Estornado. |
order.canceled |
Cancelado | Quando status_code_id muda para Cancelado. |
order.label_ready usa o evento interno RastreioRecebido. O recebimento específico de rastreio de marketplace (RastreioMarketplaceRecebido) continua atendendo o webhook v1, mas não dispara esse evento v2.
Rastreamento
| Evento | Rótulo | Status interno que dispara |
|---|---|---|
shipment.posted |
Objeto Postado | POSTED |
shipment.in_transit |
Em Trânsito | IN_TRANSIT |
shipment.out_for_delivery |
Saiu para Entrega | OUT_FOR_DELIVERY ou ON_ROUTE |
shipment.awaiting_pickup |
Aguardando Retirada | AWAITING_PICKUP |
shipment.returning |
Em Devolução | RETURNING |
shipment.delivered |
Entregue | DELIVERED |
shipment.delivery_proof |
Comprovante de Entrega | Quando ProofOfDeliveryReceived é emitido. |
shipment.returned |
Devolvido | RETURNED |
Os status de rastreamento WAITING_POSTING, RECIPIENT_ABSENT, DELIVERY_PROBLEM, LOST e CANCELLED não possuem evento v2. O listener os ignora.
Versão do schema
meta.api_version identifica a forma do corpo e só muda quando o schema muda. A versão atual é 2.
| Versão | Mudanças |
|---|---|
2 |
Adicionados shipment.tracking_url, delivery_proof.delivery_lat e delivery_proof.delivery_lng. Removido shipment.carrier.id — era a chave primária interna da tabela de transportadoras do Paylog, sem significado fora do nosso banco (a API v1 nunca a expôs). Identifique a transportadora por carrier.code. |
1 |
Versão inicial. |
carrier.id não é mais enviado. Se o seu sistema faz o de‑para por esse número, troque para carrier.code (por exemplo correios, jt-express) antes de atualizar. carrier.code é gerado a partir do nome atual da transportadora e não é um enum fixo — trate valores desconhecidos sem quebrar.
Full-schema e nulabilidade
O corpo possui schema completo: as chaves documentadas existem em todos os eventos. Dados indisponíveis são null, objetos conhecidos mantêm sua forma e items pode ser uma lista vazia. Valores monetários *_cents são inteiros em centavos.
Nesta página:
- Sempre: a chave existe em todo payload; a coluna de nulabilidade informa se o valor pode ser
null. - Condicional: a chave existe, mas só recebe objeto ou dado quando a condição é satisfeita; caso contrário vale
null. - Opcional: a chave ou header pode ser omitido. No corpo v2, nenhuma chave do schema é opcional.
Campos raiz
| Campo | Tipo | Presença | Nulabilidade e semântica |
|---|---|---|---|
event |
string | Sempre | Nunca null. Código do evento assinado pelo endpoint. |
event_label |
string | Sempre | Nunca null. Rótulo em português do evento. |
event_timestamp |
string RFC 3339 | Sempre | Nunca null. Momento em que o payload foi construído. |
idempotency_key |
string UUID | Sempre | Nunca null. Identificador para deduplicação; permanece no payload durante o reenvio da mesma entrega. |
cancelable |
booleano | Sempre | Nunca null. Calculado a partir do status atual do pedido. |
order |
objeto | Sempre | Nunca null. Dados do pedido. |
shipment |
objeto | Sempre | Nunca null; quando não há rastreio, mantém o objeto com valores null. |
meta |
objeto | Sempre | Nunca null; contém webhook_version: 2 e api_version: 2. |
order
| Campo | Tipo | Presença | Nulabilidade e semântica |
|---|---|---|---|
pedido_id |
inteiro | Sempre | Nunca null. ID interno do pedido. |
codigo_transacao |
string ou null |
Sempre | Pode ser null. Código na plataforma de origem. |
referencia_externa |
string ou null |
Sempre | Pode ser null. Referência fornecida na criação. |
plataforma |
string | Sempre | Nunca null; usa Desconhecida quando não há nome mapeado. |
status.code |
inteiro ou null |
Sempre | Pode ser null. Código do status atual do pedido. |
status.label |
string ou null |
Sempre | Pode ser null. Rótulo do status atual. |
motivo_parado |
objeto ou null |
Condicional | {id: int, label: string} somente quando o status atual é Parado e existe motivo_parado_id; caso contrário null. |
customer.nome |
string ou null |
Sempre | Pode ser null. |
customer.cpf |
string ou null |
Sempre | Pode ser null; pode conter CPF ou CNPJ conforme o cadastro. |
customer.email |
string ou null |
Sempre | Pode ser null. |
customer.telefone |
string ou null |
Sempre | Celular; quando ausente, usa telefone. |
customer.endereco.logradouro |
string ou null |
Sempre | Pode ser null. |
customer.endereco.numero |
string ou null |
Sempre | Pode ser null. |
customer.endereco.complemento |
string ou null |
Sempre | Pode ser null. |
customer.endereco.bairro |
string ou null |
Sempre | Pode ser null. |
customer.endereco.cidade |
string ou null |
Sempre | Pode ser null. |
customer.endereco.uf |
string ou null |
Sempre | Pode ser null. |
customer.endereco.cep |
string ou null |
Sempre | Pode ser null. |
customer.endereco.pais |
string | Sempre | Nunca null; usa BR quando não informado. |
items |
array | Sempre | Pode ser []. Um elemento é emitido por item do pedido. |
items[].descricao |
string ou null |
Sempre por item | Descrição do item; usa o nome do produto como fallback. |
items[].quantidade |
inteiro | Sempre por item | Conversão inteira da quantidade. |
items[].valor_unitario_cents |
inteiro ou null |
Sempre por item | Valor unitário em centavos. |
items[].produto.id |
inteiro ou null |
Sempre por item | Pode ser null para item sem produto associado. |
items[].produto.nome |
string ou null |
Sempre por item | Pode ser null. |
frete_cents |
inteiro ou null |
Sempre | Frete em centavos. |
shipment
| Campo | Tipo | Presença | Nulabilidade e semântica |
|---|---|---|---|
tracking_code |
string ou null |
Sempre | Pode ser null. |
tracking_url |
string ou null |
Sempre | Página pública de rastreamento da Paylog (/rastreio/{codigo}) — funciona com qualquer transportadora e mostra a linha do tempo junto com as imagens do comprovante. null enquanto não houver código. |
carrier.code |
string ou null |
Sempre | Slug gerado do nome atual da transportadora; não é enum fixo. |
carrier.name |
string ou null |
Sempre | Nome atual da transportadora. |
tracking_status.code |
inteiro ou null |
Sempre | Código do evento de rastreamento usado no payload ou status atual do rastreio. |
tracking_status.label |
string ou null |
Sempre | Rótulo correspondente ao status. |
description |
string ou null |
Sempre | Descrição do evento de rastreamento; pode ser null, inclusive em eventos sem tracking_event. |
location |
string ou null |
Sempre | cidade/UF, apenas cidade ou apenas UF; null sem localização. |
occurred_at |
string RFC 3339 ou null |
Sempre | Horário do evento da transportadora, diferente de event_timestamp. |
collection_address |
objeto ou null |
Condicional | Preenchido somente em AWAITING_PICKUP quando ao menos um campo do ponto de retirada existe. |
delivery_proof |
objeto | Sempre | Nunca null; todos os seus campos podem ser null. |
delivery_proof.image_url |
string ou null |
Sempre | URL da foto da entrega ou do recibo assinado. |
delivery_proof.signature_url |
string ou null |
Sempre | URL da imagem da assinatura eletrônica. |
delivery_proof.received_by |
string ou null |
Sempre | Identificação textual de quem recebeu. |
delivery_proof.signatory |
string ou null |
Sempre | Nome do signatário. |
delivery_proof.delivery_lat |
string ou null |
Sempre | Latitude da entrega, no formato da transportadora: string com vírgula decimal, por exemplo "-25,4338109". |
delivery_proof.delivery_lng |
string ou null |
Sempre | Longitude da entrega, mesmo formato. |
Quando preenchido, collection_address contém sempre estas nove chaves, cada uma podendo ser null:
| Campo | Formato |
|---|---|
unit_type |
string ou null |
logradouro |
string ou null |
numero |
string ou null |
bairro |
string ou null |
cidade |
string ou null |
uf |
string ou null |
cep |
string ou null |
pickup_deadline |
data YYYY-MM-DD ou null |
pickup_list_code |
string ou null |
Exemplo completo: shipment.awaiting_pickup
{
"event": "shipment.awaiting_pickup",
"event_label": "Aguardando Retirada",
"event_timestamp": "2026-07-22T10:15:30-03:00",
"idempotency_key": "d9428888-122b-11e1-b85c-61cd3cbb3210",
"cancelable": false,
"order": {
"pedido_id": 12345,
"codigo_transacao": "ABC123",
"referencia_externa": "pedido-987",
"plataforma": "Braip",
"status": {
"code": 18,
"label": "Aguardando Retirada"
},
"motivo_parado": null,
"customer": {
"nome": "João da Silva",
"cpf": "123.456.789-00",
"email": "[email protected]",
"telefone": "(11) 99999-9999",
"endereco": {
"logradouro": "Rua Exemplo",
"numero": "123",
"complemento": null,
"bairro": "Centro",
"cidade": "São Paulo",
"uf": "SP",
"cep": "01000-000",
"pais": "BR"
}
},
"items": [
{
"descricao": "Produto Exemplo",
"quantidade": 1,
"valor_unitario_cents": 16785,
"produto": {
"id": 42,
"nome": "Produto Exemplo"
}
}
],
"frete_cents": 1895
},
"shipment": {
"tracking_code": "AA123456789BR",
"tracking_url": "https://ev.paylog.cash/rastreio/AA123456789BR",
"carrier": {
"code": "correios",
"name": "CORREIOS"
},
"tracking_status": {
"code": 60,
"label": "Aguardando Retirada"
},
"description": "Objeto aguardando retirada no endereço indicado",
"location": "São Paulo/SP",
"occurred_at": "2026-07-22T10:10:00-03:00",
"collection_address": {
"unit_type": "Agência dos Correios",
"logradouro": "Avenida Central",
"numero": "100",
"bairro": "Centro",
"cidade": "São Paulo",
"uf": "SP",
"cep": "01000-000",
"pickup_deadline": "2026-07-29",
"pickup_list_code": "408161154094"
},
"delivery_proof": {
"image_url": null,
"signature_url": null,
"received_by": null,
"signatory": null,
"delivery_lat": null,
"delivery_lng": null
}
},
"meta": {
"webhook_version": 2,
"api_version": 2
}
}
Quando ainda não existe rastreio, o objeto shipment mantém esta forma:
{
"tracking_code": null,
"tracking_url": null,
"carrier": {
"code": null,
"name": null
},
"tracking_status": {
"code": null,
"label": null
},
"description": null,
"location": null,
"occurred_at": null,
"collection_address": null,
"delivery_proof": {
"image_url": null,
"signature_url": null,
"received_by": null,
"signatory": null,
"delivery_lat": null,
"delivery_lng": null
}
}
Campo cancelable
cancelable não é inferido pelo nome do evento. Ele é true somente quando o status atual do pedido está nesta lista:
| Código | Status |
|---|---|
1 |
Pendente |
2 |
Aguardando Faturamento |
3 |
Faturado |
4 |
Preparação |
10 |
Pago - Aguardando NF/DC |
8 |
Parado |
11 |
Correção |
13 |
Falha no Faturamento |
17 |
Etiqueta Pronta |
16 |
Aguardando Transportadora |
Em qualquer outro status, o valor é false.
Verificação HMAC-SHA256
O painel gera um segredo de 40 caracteres ao criar o endpoint e o exibe uma única vez. A assinatura é:
sha256=<HMAC-SHA256 hexadecimal do corpo JSON bruto usando o segredo>
Verifique os bytes exatos do corpo recebido. Fazer parse do JSON e serializá-lo novamente altera espaços, escapes ou ordem das chaves e invalida a assinatura.
PHP
$rawBody = file_get_contents('php://input');
$received = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
$valid = hash_equals($expected, $received);
Node.js
const crypto = require('node:crypto');
const received = req.header('X-Webhook-Signature') ?? '';
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const receivedBuffer = Buffer.from(received);
const expectedBuffer = Buffer.from(expected);
const valid = receivedBuffer.length === expectedBuffer.length
&& crypto.timingSafeEqual(receivedBuffer, expectedBuffer);
Python
import hashlib
import hmac
received = request.headers.get("X-Webhook-Signature", "")
expected = "sha256=" + hmac.new(
secret.encode(),
request.get_data(),
hashlib.sha256,
).hexdigest()
valid = hmac.compare_digest(expected, received)
Tentativas, logs e pausa automática
Uma resposta não-2xx ou um timeout/erro de conexão falha a tentativa. O job permite quatro tentativas totais:
| Tentativa | Momento | Atraso após a falha anterior |
|---|---|---|
| 1 | Imediata | — |
| 2 | Reenvio | 10 segundos |
| 3 | Reenvio | 60 segundos |
| 4 | Reenvio final | 300 segundos |
O array de backoff contém também 900, mas tries = 4; portanto não existe uma quinta tentativa e o atraso de 900 segundos não é alcançado pela configuração atual.
Cada tentativa cria um registro no histórico com evento, payload, número da tentativa, resultado, status HTTP e até 5.000 caracteres do corpo da resposta. Uma resposta 2xx zera consecutive_failures.
Pausa automática conta falhas terminais, não requisições individuais:
- Um ciclo de entrega só é terminal depois de esgotar suas até quatro tentativas.
- Ao terminar sem sucesso, o endpoint recebe uma falha consecutiva.
- Após três ciclos terminais consecutivos, o endpoint é pausado (
is_active = false). - Reativar no painel limpa o contador, remove a pausa e ativa o endpoint.
Isso representa até 12 requisições HTTP malsucedidas em três ciclos, se todas as quatro tentativas forem executadas em cada ciclo. Um sucesso entre ciclos reinicia a contagem.
Teste pelo painel
O teste usa o primeiro evento selecionado no endpoint (ou order.created como fallback), gera o mesmo full-schema com dados sintéticos e assina o corpo com o mesmo algoritmo.
Diferenças do teste:
- envia
X-Webhook-Test: true; - usa timeout de 10 segundos;
- faz uma única chamada direta, sem fila ou reenvio;
- não cria registro no histórico de entregas;
- não altera o contador de falhas nem pausa o endpoint;
- considera qualquer resposta
2xxbem-sucedida.
Resposta e idempotência
Retorne qualquer status 2xx em até 30 segundos. Respostas 3xx, 4xx, 5xx, timeout e erros de conexão entram na política de tentativas.
Persista idempotency_key e ignore uma chave já processada. Isso torna seguro receber novamente a mesma entrega: as tentativas automáticas do job, o botão Reenviar de uma entrega do histórico e uma repetição da fila reaproveitam o payload original, portanto repetem a mesma idempotency_key.
Reenvio em lote (por data / produto)
O painel também oferece um reenvio em lote por intervalo de datas e, opcionalmente, por produto. Ele não reenvia entregas antigas: reconstrói o payload a partir do estado atual do pedido. As diferenças abaixo são obrigatórias para quem consome esses eventos.
| Aspecto | Comportamento no reenvio em lote |
|---|---|
idempotency_key |
Nova a cada reenvio. Não deduplica contra a entrega original. |
| Seleção de pedidos | Todos os pedidos do produtor cujo created_at está no intervalo escolhido — filtrados pelo produto quando informado. |
| Eventos emitidos | Cada evento marcado é emitido para cada pedido selecionado, mesmo que aquele evento nunca tenha ocorrido para o pedido. |
order.* e cancelable |
Refletem o status atual do pedido, não o status do momento original. |
shipment.tracking_status |
Status atual do rastreio do pedido, não o status do evento reenviado. |
description, location, occurred_at, collection_address |
Sempre null — o reenvio não recarrega o evento de rastreamento original. |
delivery_proof |
Preenchido a partir do rastreio atual, quando houver comprovante. |
| Intervalo permitido | Últimos 30 dias pelo painel. Para períodos maiores, abra um chamado no suporte. |
Como a idempotency_key é nova, deduplicar apenas por ela não evita processar duas vezes um evento vindo de um reenvio em lote. Para esse caso, deduplique também por uma chave semântica sua, por exemplo event + order.pedido_id (+ shipment.tracking_code quando aplicável).