Вебхуки
Получайте POST-уведомление о завершении генерации с проверкой подписи HMAC-SHA256 и автоматическими ретраями.
Передайте webhook_url при постановке генерации — и по её завершении Clipia отправит POST на этот URL с результатом. Это избавляет от опроса статуса. Каждая доставка подписана HMAC-SHA256 (заголовок X-Clipia-Signature), а при сбое повторяется до 6 раз с экспоненциальной задержкой.
Payload
При успехе:
{
"request_id": "764cabcf-b745-4b3e-ae38-1200304cf45b",
"status": "OK",
"payload": {
"model": "nano-banana-2",
"output": { "images": [{ "url": "https://media.clipia.ai/works/result.png" }] },
"cost": 12
}
}При ошибке:
{
"request_id": "764cabcf-b745-4b3e-ae38-1200304cf45b",
"status": "ERROR",
"error": { "code": "GENERATION_FAILED", "message": "..." }
}Поля payload
Prop
Type
Проверка подписи
Каждая доставка несёт три заголовка:
X-Clipia-Webhook-Id: 1f2e3d4c-5b6a-7890-abcd-ef0123456789
X-Clipia-Timestamp: 1717243200
X-Clipia-Signature: t=1717243200,v1=5257a869e7...Подпись считается как HMAC_SHA256(secret, "{timestamp}.{raw_body}"), где secret — webhook signing secret из консоли разработчика. Сравнивайте подпись над сырым телом запроса (до парсинга JSON) и используйте constant-time сравнение.
import crypto from "node:crypto";
const secret = process.env.CLIPIA_WEBHOOK_SECRET;
function verify(req) {
const sig = req.headers["x-clipia-signature"]; // "t=...,v1=..."
const parts = Object.fromEntries(sig.split(",").map(p => p.split("=")));
const signed = `${parts.t}.${req.rawBody}`;
const expected = crypto.createHmac("sha256", secret).update(signed).digest("hex");
const ok = crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300; // окно 5 мин
return ok && fresh;
}import hashlib
import hmac
import os
import time
SECRET = os.environ["CLIPIA_WEBHOOK_SECRET"].encode()
def verify(headers: dict, raw_body: bytes) -> bool:
sig = headers["X-Clipia-Signature"] # "t=...,v1=..."
parts = dict(p.split("=", 1) for p in sig.split(","))
signed = f"{parts['t']}.".encode() + raw_body
expected = hmac.new(SECRET, signed, hashlib.sha256).hexdigest()
ok = hmac.compare_digest(parts["v1"], expected)
fresh = abs(time.time() - int(parts["t"])) < 300 # 5-минутное окно
return ok and freshПроверяйте свежесть
Отвергайте доставку, если X-Clipia-Timestamp отличается от текущего времени более чем на 5 минут — это защищает от повторного воспроизведения старых запросов.
Доставка и ретраи
- Отвечайте кодом
2xxв течение 10 секунд. - При не-
2xxили таймауте доставка повторяется с экспоненциальной задержкой, до 6 попыток. - История доставок видна в консоли разработчика — удобно для отладки.
Обрабатывайте идемпотентно
Одна и та же доставка может прийти повторно. Дедуплицируйте по request_id (или X-Clipia-Webhook-Id), прежде чем выполнять побочные эффекты.