Instead of polling, pass a callbackUrl (and optional webhookSecret) on any
create or post-process call. PicoBerry POSTs to your URL when the job finishes.
| Field | Type | Description |
|---|---|---|
callbackUrl | string | Public http/https URL to notify. Private / internal hosts are rejected. ≤ 2,048 chars. |
webhookSecret | string | Optional but recommended — used to sign the delivery so you can verify it. |
Delivery payload
POST <your callbackUrl>
X-PB-Delivery-Id: 7f3a...
X-PB-Signature: t=1720252800,v1=<hex>
{
"event": "asset.succeeded",
"deliveryId": "7f3a...",
"createdAt": "2026-07-06T09:00:00Z",
"data": { /* same shape as GET /v1/assets/{id} */ }
}
event is asset.succeeded or asset.failed.
Verifying the signature
X-PB-Signature is t=<unix>,v1=<hex>, where v1 is the HMAC-SHA256 of the
string "<t>.<raw-body>" keyed with your webhookSecret. Recompute it and
compare in constant time; reject if it doesn't match or t is stale.
// Node
const [t, v1] = header.split(',').map((p) => p.split('=')[1]);
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))) reject();
Idempotency
Deliveries retry with exponential backoff, so the same event may arrive more than
once. Dedupe on X-PB-Delivery-Id and make your handler idempotent.