Guides
Webhooks

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:

  • pendinguploading
  • uploadinguploaded
  • uploadedprocessing
  • processingpublished
  • 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 POST requests
  • Return 2xx within 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"
StatusMeaning
pendingQueued for delivery
deliveredYour server returned 2xx
failedAll retry attempts exhausted

Each delivery record includes the HTTP response code, response body (truncated), and number of retry attempts.