Entrar no app

Webhooks

Webhooks eliminam a necessidade de polling: quando uma geração ou batch termina, o servidor entrega o evento diretamente ao seu endpoint.

Webhooks estão disponíveis apenas em planos pagos. Uma tentativa de registro sem plano ativo retorna 403 Forbidden.

Registrar um endpoint

curl -X POST https://api.vestiai.com.br/api/v1/webhooks \
  -H "Authorization: Bearer sk_live_SUA_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://seu-backend.com/webhooks/vestiai",
    "events": ["generation.completed", "generation.failed"]
  }'

Resposta 201 Created:

{
  "id": "wh_abc123",
  "url": "https://seu-backend.com/webhooks/vestiai",
  "events": ["generation.completed", "generation.failed"],
  "enabled": true,
  "secret": "a3f1b2c4d5e6f7...",
  "createdAt": "2026-07-01T12:00:00.000Z",
  "updatedAt": "2026-07-01T12:00:00.000Z"
}

O campo secret é retornado uma única vez. Guarde-o para verificar assinaturas. A URL é validada para garantir que não aponta para endereços privados ou reservados.

Catálogo de eventos

Tipo de eventoSchema versionQuando é disparado
generation.completed1.0Geração individual concluída com sucesso
generation.failed1.0Geração individual falhou
virtual_tryon.completed1.0Virtual try-on concluído com sucesso
virtual_tryon.failed1.0Virtual try-on falhou
batch.completed1.0Batch concluído (todos os itens processados)
batch.failed1.0Batch falhou

A versão do schema é enviada no header X-VestiAI-Schema-Version e no campo schemaVersion do body. Versões minor (adição de campos opcionais) não quebram clientes existentes; versões major são comunicadas com antecedência.

Estrutura da entrega

Cada entrega é um POST para a URL registrada com os seguintes headers:

Content-Type: application/json
X-VestiAI-Signature: t=1751400000,v1=3d4e5f6a...
X-VestiAI-Schema-Version: 1.0

Body de exemplo para generation.completed:

{
  "event": "generation.completed",
  "schemaVersion": "1.0",
  "data": {
    "generationId": 1891,
    "userId": "usr_xyz",
    "status": "done",
    "resultUrl": "https://cdn.vestiai.com.br/..."
  }
}

Verificação de assinatura

Use sempre o body raw (bytes) antes de parsear JSON. A assinatura é calculada sobre o body antes da desserialização.

O header X-VestiAI-Signature tem o formato:

t=<unix_seconds>,v1=<hex_sig>[,v1=<hex_sig_anterior>]

Algoritmo de verificação (Node.js como referência):

import { createHmac, timingSafeEqual } from 'node:crypto';

function verifyVestiAISignature(rawBody, sigHeader, secret) {
  const parts = sigHeader.split(',');
  const tPart = parts.find(p => p.startsWith('t='));
  if (!tPart) return false;

  const timestamp = parseInt(tPart.slice(2), 10);
  // Rejeite entregas com mais de 5 minutos de defasagem (replay attack)
  if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;

  const payload = `${timestamp}.${rawBody}`;
  const expected = createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  const v1Sigs = parts
    .filter(p => p.startsWith('v1='))
    .map(p => p.slice(3));

  // Aceita se QUALQUER v1 corresponder (janela de rotacao)
  return v1Sigs.some(sig => {
    try {
      return timingSafeEqual(Buffer.from(sig, 'hex'), Buffer.from(expected, 'hex'));
    } catch {
      return false;
    }
  });
}

Passos:

  1. Extraia t do header.
  2. Rejeite se |now - t| > 300s.
  3. Compute HMAC-SHA256(secret, "<t>.<raw_body>") em hex.
  4. Compare com tempo constante (timingSafeEqual) contra todos os valores v1. Aceite se qualquer um bater.
  5. Retorne 2xx rapidamente; processe de forma assíncrona.

Rotação de segredo (dual-signature)

Ao rotacionar o segredo via POST /api/v1/webhooks/:id/rotate-secret, o servidor retorna o novo segredo e um timestamp de expiração:

{
  "id": "wh_abc123",
  "secret": "b4c5d6e7f8a9b0...",
  "previousSecretExpiresAt": "2026-07-02T12:00:00.000Z"
}

Durante a janela de 24 horas após a rotação, todas as entregas são assinadas com ambos os segredos. O header terá dois valores v1:

X-VestiAI-Signature: t=1751400000,v1=<novo>,v1=<anterior>

Isso permite atualizar o segredo no seu backend sem downtime: seu código aceita a entrega verificando contra o segredo antigo, e depois que você atualizar para o novo, ambos continuam funcionando até a janela expirar.

Gerenciamento de webhooks

OperaçãoEndpoint
RegistrarPOST /api/v1/webhooks
ListarGET /api/v1/webhooks
DetalharGET /api/v1/webhooks/:id
AtualizarPATCH /api/v1/webhooks/:id
Desativar/deletarDELETE /api/v1/webhooks/:id
Enviar ping de testePOST /api/v1/webhooks/:id/ping
Rotacionar segredoPOST /api/v1/webhooks/:id/rotate-secret
Listar entregasGET /api/v1/webhooks/:id/deliveries
Replay de entregaPOST /api/v1/webhooks/:id/deliveries/:dId/replay

DELETE é uma soft-delete: o webhook para de receber eventos e desaparece da listagem, mas o histórico de entregas é preservado para audit e replay.

Replay

Para reprocessar uma entrega com falha:

curl -X POST https://api.vestiai.com.br/api/v1/webhooks/wh_abc123/deliveries/dlv_xyz/replay \
  -H "Authorization: Bearer sk_live_SUA_CHAVE"

Um novo registro de entrega é criado com o mesmo eventType e payload do original. O registro original é mantido intacto para auditoria.

Boas práticas

  • Responda 2xx em até 30 segundos. Processe de forma assíncrona.
  • Implemente idempotência no seu handler: o servidor pode retentar uma entrega.
  • Use o campo requestId do log de entrega para correlacionar com seus próprios logs.
  • Verifique a assinatura antes de processar o payload.

Para detalhes sobre limites de requisições, veja Rate limits e idempotência. Para autenticação por API key, veja Autenticação.