Skip to main content
Webhooks permitem que a Voop notifique seu sistema em tempo real quando algo muda no catálogo. Use-os para sincronização bidirecional, dashboards de auditoria, ou triggers de fluxos internos.

Eventos disponíveis

EventoDisparado quando
item.createdNovo item criado via API
item.updatedItem existente alterado (contentHash mudou)
item.deletedItem removido
variant.created / .updated / .deletedVariantes (futuro)
stock.adjustedMovimento de estoque registrado
media.ingestedImagem baixada com sucesso
media.ingestion_failedImagem falhou após retries
import.completedBulk import finalizou (com ou sem erros)
import.failedBulk import abortou
Use "*" no array events para subscrever todos.

Formato do payload

POST https://seu-servidor.com/webhook
Content-Type: application/json
X-Voop-Signature: t=1714000000,v1=a1b2c3...
X-Voop-Event-Type: item.created
X-Voop-Event-Id: 01HXYZ...
User-Agent: Voop-Webhooks/1.0

{
  "id": "01HXYZ...",
  "type": "item.created",
  "catalogId": "catalog-uuid",
  "resourceId": "PROD-123",
  "createdAt": "2026-04-25T12:34:56.789Z",
  "data": {
    "externalId": "PROD-123",
    "externalSystem": "shopify",
    "voopId": "uuid-internal",
    "sku": "ABC-001"
  }
}

Verificação da assinatura

A assinatura usa HMAC SHA-256 com timestamp para prevenir replay.
header   = "t={timestamp},v1={hex_signature}"
payload  = `${timestamp}.${rawBody}`
signature = HMAC-SHA256(secret, payload)

Exemplo (Node.js)

import crypto from 'crypto';

function verifyVoopWebhook(req, secret) {
  const header = req.headers['x-voop-signature'];
  const [tPart, v1Part] = header.split(',');
  const timestamp = parseInt(tPart.split('=')[1], 10);
  const provided = v1Part.split('=')[1];

  // Replay protection: rejeitar se timestamp > 5 minutos
  if (Math.abs(Date.now() / 1000 - timestamp) > 300) {
    throw new Error('Webhook timestamp too old');
  }

  const rawBody = req.rawBody; // bytes brutos, antes do JSON.parse
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  // timing-safe compare!
  if (!crypto.timingSafeEqual(Buffer.from(provided), Buffer.from(expected))) {
    throw new Error('Invalid signature');
  }
}
Sempre use timingSafeEqual (ou equivalente). Comparação de strings comum vaza tempo e permite ataques de timing.
Use o body raw, não o JSON re-serializado. Frameworks como Express com express.json() perdem o body original — você precisa de express.raw() ou capturar rawBody antes do middleware de JSON.

Retries e backoff

Se sua URL não retornar 2xx em 5 segundos, a Voop retenta:
attempt 1  →  imediato
attempt 2  →  ~5s
attempt 3  →  ~25s
attempt 4  →  ~2min
attempt 5  →  ~10min
attempt 6  →  ~30min
attempt 7  →  ~2h
attempt 8  →  ~6h
após 8     →  status="exhausted"

Auto-disable

Se um webhook acumula 20 falhas terminais consecutivas (i.e., exhausted), ele é automaticamente desabilitado (status: disabled) e o owner recebe email. Para reativar: POST /webhooks/{id}/resume (após corrigir o problema). Ou pelo Developer Portal.

Idempotência no consumidor

A Voop pode entregar o mesmo evento mais de uma vez (em cenários de timeout seguido de sucesso na próxima tentativa). Sua implementação deve ser idempotente:
  • Use o X-Voop-Event-Id (UUID) como chave de dedup
  • Guarde IDs vistos por algum tempo (ex: Redis TTL 7 dias)
  • Skip se já processado

Replay manual

Falhou uma entrega importante? POST /webhooks/{id}/deliveries/{deliveryId}/retry re-enfileira a mesma entrega. (Disponível também na UI do Developer Portal.)