Conceitos
Assíncrono & webhooks
Toda geração roda em segundo plano. Descubra quando terminou por polling ou por um webhook assinado.
O fluxo
POST /generations→ um job comstatus: "processing".- Polling em
GET /generations/:idatécompleted/failed— ou passe umwebhook_urle seja notificado.
Polling
Simples e sem setup. Consulte o job a cada poucos segundos:
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:
| Header | Valor |
|---|---|
X-Caranguejo-Event | generation.completed | generation.failed |
X-Caranguejo-Signature | sha256=<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:
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é 3× 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.