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ção | Endpoint |
|---|---|
| Criar | POST /api/v0/webhook |
| Listar | GET /api/v0/webhook |
| Atualizar | PUT /api/v0/webhook/{id} |
| Remover | DELETE /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.
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
| Evento | Quando é disparado | data.status |
|---|---|---|
order.created | A etiqueta foi criada e aguarda pagamento | created |
order.released | A etiqueta foi paga | released |
order.generated | A etiqueta foi gerada na transportadora e já tem código de rastreio | generated |
order.posted | O pacote foi postado (ou coletado) | posted |
order.delivered | O pacote foi entregue no destino | delivered |
order.cancelled | A etiqueta foi cancelada | cancelled |
Assine só os eventos que você usa, no campo events do Webhook App. Um Webhook App sem eventos não recebe nada.
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:
{
"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:
ideorder_idtrazem o mesmo valor: o ID da etiqueta na SuperFrete, o mesmo devolvido na criação.trackingvem preenchido a partir do eventoorder.generated.tracking_urlsó vem nos eventosorder.postedeorder.delivered.tagstraz as identificações que você enviou emoptions.tagsao criar a etiqueta: cadatagenviada volta emvalue, comnameigual aorder_ide aurl, 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://(ouhttp://), ser pública e aceitarPOST. Endereçoslocalhost, de rede interna ou atrás de login não recebem as notificações. - Responda com qualquer status
2xxem 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.idpara 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.
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
- 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.
- Cadastre um Webhook App no sandbox apontando para essa URL.
- 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.
- 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.
- Calcule o HMAC-SHA256 desse corpo usando o
secret_tokencomo chave. - Compare o resultado com o header
X-ME-Signatureusando uma comparação de tempo constante. Se forem iguais, a notificação é válida.
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();
});
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
$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);
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.