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 evento | Schema version | Quando é disparado |
|---|---|---|
generation.completed | 1.0 | Geração individual concluída com sucesso |
generation.failed | 1.0 | Geração individual falhou |
virtual_tryon.completed | 1.0 | Virtual try-on concluído com sucesso |
virtual_tryon.failed | 1.0 | Virtual try-on falhou |
batch.completed | 1.0 | Batch concluído (todos os itens processados) |
batch.failed | 1.0 | Batch 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:
- Extraia
tdo header. - Rejeite se
|now - t| > 300s. - Compute
HMAC-SHA256(secret, "<t>.<raw_body>")em hex. - Compare com tempo constante (
timingSafeEqual) contra todos os valoresv1. Aceite se qualquer um bater. - Retorne
2xxrapidamente; 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ção | Endpoint |
|---|---|
| Registrar | POST /api/v1/webhooks |
| Listar | GET /api/v1/webhooks |
| Detalhar | GET /api/v1/webhooks/:id |
| Atualizar | PATCH /api/v1/webhooks/:id |
| Desativar/deletar | DELETE /api/v1/webhooks/:id |
| Enviar ping de teste | POST /api/v1/webhooks/:id/ping |
| Rotacionar segredo | POST /api/v1/webhooks/:id/rotate-secret |
| Listar entregas | GET /api/v1/webhooks/:id/deliveries |
| Replay de entrega | POST /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
2xxem até 30 segundos. Processe de forma assíncrona. - Implemente idempotência no seu handler: o servidor pode retentar uma entrega.
- Use o campo
requestIddo 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.