Pular para o conteúdo principal

Webhooks

Com webhooks, a SuperFrete avisa a sua aplicação quando algo acontece com um pedido: criado, pago, etiqueta gerada, postado, entregue ou cancelado. Você cadastra um Webhook App com a URL que vai receber as notificações, e nós enviamos um POST com os dados do evento.

Gerenciando Webhook Apps​

AçãoEndpoint
CriarPOST /api/v0/webhook
ListarGET /api/v0/webhook
AtualizarPUT /api/v0/webhook/{id}
RemoverDELETE /api/v0/webhook/{id}

Todos exigem o mesmo token das outras chamadas, no header Authorization: Bearer {token}. Gere o token em produção ou no sandbox.

Ao criar um Webhook App, guarde o secret_token da resposta: é com ele que você valida a assinatura de cada notificação.

Criar um Webhook App no sandbox
curl --request POST \
--url https://sandbox.superfrete.com/api/v0/webhook \
--header "Authorization: Bearer $SUPERFRETE_TOKEN" \
--header "User-Agent: MinhaLoja/1.0 (dev@minhaloja.com.br)" \
--header "Content-Type: application/json" \
--data '{
"name": "Minha loja",
"url": "https://minhaloja.com.br/webhooks/superfrete",
"events": ["order.posted", "order.delivered"]
}'

Eventos​

EventoQuando é disparadodata.status
order.createdA etiqueta foi criada e aguarda pagamentocreated
order.releasedA etiqueta foi pagareleased
order.generatedA etiqueta foi gerada na transportadora e já tem código de rastreiogenerated
order.postedO pacote foi postado (ou coletado)posted
order.deliveredO pacote foi entregue no destinodelivered
order.cancelledA etiqueta foi canceladacancelled

Assine só os eventos que você usa, no campo events do Webhook App. Um Webhook App sem eventos não recebe nada.

cancelled ou canceled?

Nos webhooks, o evento e o status de cancelamento são escritos com dois "l": order.cancelled e cancelled. Na API de pedidos, o status é canceled, com um "l". Compare com o valor certo em cada lugar.

Formato da notificação​

Cada notificação é um POST com estes headers:

Content-Type: application/json
User-Agent: SuperFrete Webhooks/1.0
X-ME-Signature: 5d41402abc4b2a76b9719d911017c592...

E este corpo:

POST na sua URL
{
"event": "order.delivered",
"data": {
"id": "r8p3dhqjn4I0tBpnvkpU",
"order_id": "r8p3dhqjn4I0tBpnvkpU",
"protocol": null,
"status": "delivered",
"tracking": "AA923452383BR",
"tracking_url": "rastreio.superfrete.com/#/tracking/AA923452383BR",
"self_tracking": null,
"user_id": "[uid]",
"tags": [{ "name": "order_id", "value": "3101481", "url": "https://minhaloja.com.br/pedidos/3101481" }],
"created_at": "2024-03-29T23:49:26+00:00",
"paid_at": "2024-03-29T23:50:47+00:00",
"generated_at": "2024-03-29T23:51:47+00:00",
"posted_at": "2024-03-29T23:55:00+00:00",
"delivered_at": "2024-03-29T23:57:47+00:00",
"canceled_at": null,
"expired_at": null
}
}

Alguns detalhes dos campos:

  • id e order_id trazem o mesmo valor: o ID da etiqueta na SuperFrete, o mesmo devolvido na criação.
  • tracking vem preenchido a partir do evento order.generated. tracking_url só vem nos eventos order.posted e order.delivered.
  • tags traz as identificações que você enviou em options.tags ao criar a etiqueta: cada tag enviada volta em value, com name igual a order_id e a url, se você informou uma.
  • canceled_at é preenchido quando a etiqueta é cancelada por alguém. expired_at, quando o próprio sistema cancela a etiqueta, por exemplo por falta de pagamento.

Entrega​

  • A sua URL precisa começar com https:// (ou http://), ser pública e aceitar POST. Endereços localhost, de rede interna ou atrás de login não recebem as notificações.
  • Responda com qualquer status 2xx em até 10 segundos. Se precisar de processamento pesado, confirme o recebimento primeiro e processe depois, em uma fila.
  • Cada evento é enviado uma vez por pedido para cada Webhook App. Mesmo assim, trate o recebimento como idempotente: use event + data.id para ignorar uma notificação que você já processou.
  • Os eventos podem chegar fora de ordem. Antes de atualizar o pedido, compare as datas (paid_at, posted_at, delivered_at) com o que você já tem.
Não dependa só do webhook

Se a sua URL estiver fora do ar ou responder com erro quando o evento for enviado, aquela notificação pode não ser reenviada. Para não perder nenhuma mudança, rode de tempos em tempos uma conciliação: para os pedidos que ainda não foram entregues nem cancelados, consulte GET /api/v0/order/info/{id} e atualize o status.

Testando​

  1. Gere uma URL pública temporária em um serviço como webhook.site, ou exponha o seu servidor local com uma ferramenta de túnel.
  2. Cadastre um Webhook App no sandbox apontando para essa URL.
  3. Crie e pague uma etiqueta no sandbox, seguindo Sua primeira etiqueta. Os eventos de criação, pagamento e geração chegam em poucos instantes.

No sandbox, nenhum pacote é postado nem entregue de verdade, então order.posted e order.delivered normalmente não chegam por lá.

Validando a assinatura​

Toda notificação traz o header X-ME-Signature: um HMAC-SHA256, em hexadecimal, calculado sobre o corpo da requisição com o secret_token do seu Webhook App. Validar essa assinatura garante que a notificação veio da SuperFrete e não foi alterada no caminho.

  1. Capture o corpo bruto da requisição, exatamente como chegou. Não faça parse e re-serialize o JSON antes de validar: qualquer diferença, até um espaço, muda a assinatura.
  2. Calcule o HMAC-SHA256 desse corpo usando o secret_token como chave.
  3. Compare o resultado com o header X-ME-Signature usando uma comparação de tempo constante. Se forem iguais, a notificação é válida.
Node.js (Express)
import crypto from 'node:crypto';
import express from 'express';

const app = express();

// express.raw mantém o corpo exatamente como chegou, sem parse.
app.post('/webhooks/superfrete', express.raw({ type: 'application/json' }), (req, res) => {
const received = req.get('X-ME-Signature') ?? '';
const expected = crypto
.createHmac('sha256', process.env.SUPERFRETE_WEBHOOK_SECRET)
.update(req.body)
.digest('hex');

const valid =
received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
if (!valid) return res.status(401).end();

const { event, data } = JSON.parse(req.body.toString('utf8'));
// Enfileire o processamento e responda logo.
res.status(200).end();
});
Python (Flask)
import hashlib
import hmac
import os

from flask import Flask, abort, request

app = Flask(__name__)
SECRET = os.environ["SUPERFRETE_WEBHOOK_SECRET"].encode()

@app.post("/webhooks/superfrete")
def superfrete_webhook():
body = request.get_data() # corpo bruto, sem parse
expected = hmac.new(SECRET, body, hashlib.sha256).hexdigest()
received = request.headers.get("X-ME-Signature", "")
if not hmac.compare_digest(received, expected):
abort(401)

payload = request.get_json()
# Enfileire o processamento e responda logo.
return "", 200
PHP
<?php
$body = file_get_contents('php://input'); // corpo bruto, sem parse
$received = $_SERVER['HTTP_X_ME_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $body, getenv('SUPERFRETE_WEBHOOK_SECRET'));

if (!hash_equals($expected, $received)) {
http_response_code(401);
exit;
}

$payload = json_decode($body, true);
// Enfileire o processamento e responda logo.
http_response_code(200);
Mantenha o secret_token no servidor

O secret_token é uma chave secreta. Guarde em variável de ambiente ou cofre de segredos, nunca no front-end nem no repositório.

O contrato completo da notificação, com todos os campos, está na referência da API.

Ficou com alguma dúvida? Escreva para integracao@superfrete.com ou fale com a gente pelo chat.