配信

Webhook

ポーリングは一切不要です。作成または後処理のジョブにcallbackUrlを指定すると、アセットが完了した瞬間にPicoBerryが署名付きイベントをサーバーへPOSTします。

ジョブで有効にする

任意の生成リクエストに、オプションのフィールドを2つ追加します — from-textfrom-imageimagesリメッシュテクスチャアニメーション:

パラメータ説明
callbackUrl任意
string
完了したアセットをPOSTする公開https://URL。プライベート/内部IPは拒否されます。最大2,048文字。
webhookSecret任意
string
HMAC署名シークレット。設定すると、配信に検証可能なX-PB-Signatureヘッダーが付与されます。強く推奨します。
コールバック付きで作成
curl -X POST https://api.picoberry.ai/v1/models/from-text \
  -H "Authorization: Bearer pb_live_xxx" -H "Content-Type: application/json" \
  -d '{"prompt":"a stylized treasure chest","engine":"tripo",
       "callbackUrl":"https://example.com/webhooks/picoberry",
       "webhookSecret":"whsec_your_secret"}'
requests.post(f"{BASE}/v1/models/from-text", headers=headers, json={
    "prompt": "a stylized treasure chest", "engine": "tripo",
    "callbackUrl": "https://example.com/webhooks/picoberry",
    "webhookSecret": "whsec_your_secret"})
await fetch(`${BASE}/v1/models/from-text`, { method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({ prompt: "a stylized treasure chest", engine: "tripo",
    callbackUrl: "https://example.com/webhooks/picoberry",
    webhookSecret: "whsec_your_secret" }) });

イベント

ジョブが終了状態に達すると、PicoBerryはイベントを1つ配信します:

asset.succeeded アセット完了 — filesが利用可能asset.failed ジョブ失敗 — クレジットは返金済み

配信

リクエストボディのdataは、GET /v1/assets/{id}から得られるものとバイト単位で同一のオブジェクトです。

POST <your callbackUrl>
X-PB-Event: asset.succeeded
X-PB-Delivery-Id: 7f3a1b2c-…         # idempotency key — dedupe on this
X-PB-Signature: t=1785920000,v1=<hex>   # present when webhookSecret was set
Content-Type: application/json

{
  "event": "asset.succeeded",
  "deliveryId": "7f3a1b2c-…",
  "createdAt": "2026-08-05T09:12:00.000Z",
  "data": { /* identical to GET /v1/assets/{id} — id, taskStatus, files, … */ }
}

署名を検証する

v1は、webhookSecretをキーとした文字列"<t>.<raw-request-body>"HMAC-SHA256です。生のボディ(JSONパース前)で再計算し、一定時間で比較し、リプレイを防ぐために古いタイムスタンプ(> 5分)は拒否してください。X-PB-Delivery-Idで重複を除去してください。

Node.js (express)
const crypto = require("crypto");

function verify(header, rawBody, secret) {
  const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
  const expected = crypto.createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}
Python (flask)
import hmac, hashlib, time

def verify(header, raw_body, secret):
    parts = dict(p.split("=") for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{parts['t']}.{raw_body}".encode(),
                        hashlib.sha256).hexdigest()
    return hmac.compare_digest(parts["v1"], expected)
すばやく応答する 数秒以内に2xxを返し、重い処理は非同期で行ってください。2xx以外の応答とタイムアウトは指数バックオフで再試行されるため、X-PB-Delivery-Idを使ってハンドラーを冪等にしてください。