Conceitos

Assíncrono & webhooks

Toda geração roda em segundo plano. Descubra quando terminou por polling ou por um webhook assinado.

O fluxo

  1. POST /generations → um job com status: "processing".
  2. Polling em GET /generations/:id até completed / failedou passe um webhook_url e seja notificado.

Polling

Simples e sem setup. Consulte o job a cada poucos segundos:

bash
ID=9f0c1a7e-…
# repita até status = completed | failed
curl -s https://caranguejo.art/api/v1/dev/generations/$ID \
  -H "Authorization: Bearer $CK" | jq '.data.status'

Webhooks assinados

Inclua webhook_url (público, https) na criação do job para receber um POST quando ele chegar a um estado terminal. Eventos: generation.completed e generation.failed. Headers da entrega:

HeaderValor
X-Caranguejo-Eventgeneration.completed | generation.failed
X-Caranguejo-Signaturesha256=<hex> — HMAC-SHA256 do corpo bruto

O corpo é um WebhookEvent com event, created_at e data (a própria Generation).

Verificando a assinatura

Calcule o HMAC-SHA256 do corpo bruto (não do JSON re-serializado) com o seu segredo whsec_… (Conta → Apps & API) e compare em tempo constante:

node
import crypto from "node:crypto";

function verify(rawBody, signature, secret) {
  const expected = "sha256=" +
    crypto.createHmac("sha256", secret).update(rawBody, "utf8").digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

// Express: use o corpo BRUTO, não o já parseado
app.post("/webhooks/caranguejo", express.raw({ type: "*/*" }), (req, res) => {
  const sig = req.header("X-Caranguejo-Signature");
  if (!verify(req.body, sig, process.env.WEBHOOK_SECRET)) return res.sendStatus(401);
  const evt = JSON.parse(req.body.toString("utf8"));
  // evt.event, evt.data.output.assets[…]
  res.sendStatus(200);
});
Reentregamos até com backoff em erro de rede / 5xx / 429. Responda 2xx rápido e processe de forma idempotente — a mesma entrega pode chegar mais de uma vez.