Webhooks
Receive real-time notifications when assignment status changes, instead of polling.
What triggers a webhook
A webhook is sent on every status transition of an assignment:
pending→uploadinguploading→uploadeduploaded→processingprocessing→published- Any state →
failed - Any state →
cancelled
Setting up a webhook endpoint
Provide a callbackUrl when creating an assignment:
{
"connectionId": "...",
"type": "video",
"payload": {...},
"callbackUrl": "https://your-server.com/webhook/delivery"
}Your endpoint must:
- Accept
POSTrequests - Return
2xxwithin 10 seconds - Be publicly reachable (no localhost or VPN-only addresses)
Webhook payload
{
"assignmentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "published",
"platform": "youtube",
"connectionId": "7f3e1a2b-4c5d-6e7f-8a9b-0c1d2e3f4a5b",
"platformPostId": "dQw4w9WgXcQ",
"platformUrl": "https://youtu.be/dQw4w9WgXcQ",
"errors": [],
"timestamp": "2026-04-23T10:02:35Z"
}For failed status, errors contains details:
{
"assignmentId": "...",
"status": "failed",
"errors": [
{"code": "UPLOAD_FAILED", "message": "Media file returned 403"}
],
"timestamp": "2026-04-23T10:01:00Z"
}Verifying the signature
Every webhook request includes an HMAC-SHA256 signature in the X-Webhook-Signature header. Always verify it before processing the payload.
The signature is computed as:
HMAC-SHA256(webhook_secret, raw_request_body)const crypto = require('crypto');
function verifyWebhook(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express example
app.post('/webhook/delivery', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-webhook-signature'];
if (!verifyWebhook(req.body, sig, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
// process event...
res.sendStatus(200);
});Finding your webhook secret
The webhook secret is shown in Settings → Credentials (opens in a new tab) in the console. You can rotate it via POST /api/v1/admin/api-clients/{id}/rotate-webhook-secret (admin only).
Best practices
Respond quickly, process asynchronously. Return 200 immediately and enqueue the event for background processing. If your handler takes more than 10 seconds, the delivery will be marked as failed and retried.
Make your handler idempotent. The same event may be delivered more than once (on retry). Use assignmentId + status as the idempotency key.
Handle retries gracefully. Failed deliveries are retried with exponential backoff. Don't create side effects that can't be undone if the same event arrives twice.
Delivery log
Inspect all webhook delivery attempts:
# All deliveries
curl "https://pharlo.io/api/v1/webhooks/deliveries" \
-H "Authorization: Bearer $DELIVERY_API_KEY"
# Filter by status
curl "https://pharlo.io/api/v1/webhooks/deliveries?status=failed" \
-H "Authorization: Bearer $DELIVERY_API_KEY"| Status | Meaning |
|---|---|
pending | Queued for delivery |
delivered | Your server returned 2xx |
failed | All retry attempts exhausted |
Each delivery record includes the HTTP response code, response body (truncated), and number of retry attempts.