전달

웹훅

폴링을 완전히 건너뛰세요. 생성 또는 후처리 작업에 callbackUrl을 전달하면, 자산이 완료되는 즉시 PicoBerry가 서명된 이벤트를 여러분의 서버로 POST합니다.

작업에 활성화하기

어떤 생성 요청에든 선택 필드 두 개를 추가하세요 — from-text, from-image, images, 리메시, 텍스처, 애니메이션:

파라미터설명
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가 이벤트 하나를 전달합니다:

asset.succeeded 자산 완료 — files 준비 완료asset.failed 작업 실패 — 크레딧 환불됨

전달

요청 본문의 dataGET /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, … */ }
}

서명 검증

v1webhookSecret을 키로 사용한 문자열 "<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를 사용해 핸들러를 멱등하게 만드세요.