Webhooks
Cria, lista, lê e remove assinaturas de webhook para receber os eventos da loja no seu sistema.
Um webhook é um endereço seu que a plataforma chama (um POST com JSON) quando algo acontece na loja: um pedido foi criado, um produto mudou, um carrinho foi abandonado. É a forma de manter um ERP, um CRM ou um sistema de logística em dia sem ficar consultando a API de tempos em tempos.
Estas operações gerenciam as assinaturas: qual endereço recebe quais eventos. Os eventos aceitos estão listados no campo events da criação, e a verificação da assinatura de cada envio está descrita mais abaixo, em Como cada envio é assinado.
Na v2 uma assinatura não pode ser alterada. Para mudar o endereço ou os eventos, apague e crie de novo: a nova assinatura recebe um segredo novo.
/api/v2/webhooksAdminListar assinaturas
Lista as assinaturas da loja. O segredo vem mascarado (só os 4 últimos caracteres).
Parâmetros de consulta
pageintegerpadrão: 1per_pageintegerpadrão: 20fieldsstring/api/v2/webhooks/{id}AdminLer uma assinatura
Devolve uma assinatura, com o segredo mascarado.
Parâmetros de caminho
iduuidobrigatório/api/v2/webhooksAdminCriar uma assinatura
Cria a assinatura, já ativa. A resposta traz o segredo completo uma única vez: guarde-o agora, é com ele que você confere a assinatura de cada envio. Nas leituras seguintes o segredo vem mascarado.
Corpo da requisição
urlstringobrigatóriohttps (ou http) que vai receber os eventos. Use https. Endereços internos, de loopback ou de rede privada são recusados.eventsarrayobrigatórioorder.created, order.statusChanged, order.completed, order.cancelled, order.shipped, product.created, product.updated, product.deleted, customer.registered, customer.updated, cart.abandoned, review.created, vendor.registered, vendor.approved, e os de incentivos incentive.campaign_published, incentive.member_enrolled, incentive.progress_changed, incentive.award_granted, incentive.redemption_updated, incentive.campaign_closed, incentive.resource_changed.descriptionstring/api/v2/webhooks/{id}AdminRemover uma assinatura
Remove a assinatura e o histórico de envios dela. Os eventos deixam de ser enviados na hora.
Parâmetros de caminho
iduuidobrigatórioExemplo de requisição
curl -X POST "$BASE_URL/api/v2/webhooks" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://erp.minhaempresa.com.br/hooks/venddor",
"events": [
"order.created",
"order.shipped"
],
"description": "Integração com o ERP"
}'Resposta
{
"data": {
"id": "6f4e1b01-bc9a-41cc-8897-ab73c7efe3e5",
"url": "https://erp.minhaempresa.com.br/hooks/venddor",
"events": ["order.created", "order.shipped"],
"secret": "e6f6c80f05ab7f44153d5f0effa269266b4c0732c95174aa20e1014e2512b477",
"is_active": true,
"description": "Integração com o ERP",
"created_at": "2026-10-11T21:10:09.466263068Z",
"updated_at": "2026-10-11T21:10:09.466263068Z"
}
}{
"data": {
"id": "6f4e1b01-bc9a-41cc-8897-ab73c7efe3e5",
"url": "https://erp.minhaempresa.com.br/hooks/venddor",
"events": ["order.created", "order.shipped"],
"secret": "****b477",
"is_active": true,
"description": "Integração com o ERP",
"created_at": "2026-10-11T21:10:09.466263Z",
"updated_at": "2026-10-11T21:10:09.466263Z"
}
}Campos da resposta
data.iduuiddata.urlstringdata.eventsarraydata.secretstring**** e os 4 últimos caracteres.data.is_activebooleandata.descriptionstringdata.created_atstringdata.updated_atstringA lista (GET /api/v2/webhooks) devolve data como vetor desses objetos mais meta.pagination. A remoção responde 204 sem corpo.
Erros
| Status | Quando | Corpo |
|---|---|---|
| 400 | url ausente | {"error":{"code":"bad_request","message":"url is required"}} |
| 400 | Endereço interno ou privado | {"error":{"code":"bad_request","message":"webhook url not allowed: ssrf_forbidden: url targets internal/private/loopback address"}} |
| 400 | events vazio | {"error":{"code":"bad_request","message":"at least one event is required"}} |
| 400 | Evento que não existe | {"error":{"code":"bad_request","message":"unsupported event type: foo"}} |
| 404 | Assinatura inexistente | {"error":{"code":"not_found","message":"webhook not found"}} |
Como cada envio é assinado
Cada envio é um POST com corpo JSON no formato {event, payload, timestamp, webhook_id} e com estes cabeçalhos:
| Cabeçalho | Valor |
|---|---|
Content-Type | application/json |
X-Venddor-Signature | sha256= seguido de 64 caracteres hexadecimais |
X-Venddor-Event | Nome do evento, por exemplo order.created |
User-Agent | Venddor-Webhook/1.0 |
Para confirmar que o envio veio da plataforma e não foi alterado, refaça a conta no seu receptor:
- Leia o corpo bruto, os bytes exatos que chegaram, antes de qualquer parser de JSON.
- Calcule
HMAC-SHA256(secret, corpo), usando osecretda assinatura como texto (os 64 caracteres), sem decodificar de hexadecimal. - Escreva o resultado em hexadecimal minúsculo, com o prefixo
sha256=, e compare com o cabeçalho usando uma função de tempo constante. - Recuse envios cujo
timestampdifira mais de 5 minutos do seu relógio.
import crypto from "node:crypto";
function assinaturaValida(rawBody, header, secret) {
const esperado = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(header || "");
const b = Buffer.from(esperado);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}