Webhooks: get the result of every upload
Webhook events and payload, the HMAC-SHA256 signature headers, a Node.js verification example, automatic retries and the test event in API & webhooks.
A webhook calls your URL as soon as something happens – a post was published or failed, an account was connected or needs to be reconnected. You no longer need to poll the status endpoint.
Set up a webhook
- Open API & webhooks.
- Under Webhooks click New webhook.
- Enter an HTTPS URL that is reachable from the internet and choose the events.
- Click Create and copy the signing secret – it is shown only once.
The Upload-Post compatible API offers the same: POST /api/uploadposts/users/notifications with webhook_url and webhook_events; a new secret comes from POST /api/uploadposts/users/webhook-secret. The number of webhooks depends on your plan.
Events
| Event | When |
|---|---|
upload_completed |
a platform finished a post – successfully or not (one event per platform) |
social_account_connected |
an account was connected or reconnected |
social_account_disconnected |
an account was removed |
social_account_reauth_required |
an access expired and the account must be reconnected |
Payload
Every delivery is a POST with a JSON body:
{
"event": "upload_completed",
"request_id": "2c0f…",
"job_id": null,
"external_id": "video-123",
"profile_username": "my-brand",
"platform": "youtube",
"media_type": "video",
"title": "My video",
"caption": "",
"result": { "success": true, "url": "https://www.youtube.com/watch?v=…", "post_id": "…" },
"user_email": "owner@example.com",
"created_at": "2026-11-07T12:00:03.412Z"
}
If the platform added a warning (for example YouTube set a video to private), it is in result.warnings.
Check the signature
Every delivery carries these headers:
X-VA-SignatureandX-Upload-Post-Signature– bothsha256=followed by the hex HMAC-SHA256 of"<timestamp>.<raw body>"with your signing secretX-Upload-Post-Timestamp– Unix time in secondsX-Upload-Post-Event– the event name
Compute the signature over the raw request body, before any JSON parsing, and compare in constant time. Reject old timestamps to block replays.
// Node.js (Express): app.post('/hook', express.raw({ type: 'application/json' }), handler)
const crypto = require('crypto');
function verify(req, secret) {
const ts = req.get('X-Upload-Post-Timestamp');
const sig = req.get('X-VA-Signature') || req.get('X-Upload-Post-Signature') || '';
if (!ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(`${ts}.${req.body.toString('utf8')}`).digest('hex');
return sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
function handler(req, res) {
if (!verify(req, process.env.VA_WEBHOOK_SECRET)) return res.sendStatus(401);
const event = JSON.parse(req.body.toString('utf8'));
// … your logic …
res.sendStatus(200);
}
Retries
Answer with any 2xx status within a few seconds. If your server is unreachable or answers with an error, we try again up to 6 times with growing intervals. Work that takes longer should be done after you have answered. Recent deliveries with status code and error are listed under Recent deliveries in API & webhooks.
Because of retries an event can arrive twice. Use request_id plus platform to recognise duplicates.
Send a test event
Click Send test event in API & webhooks. Every webhook gets an upload_completed event with "test": true and a dummy YouTube URL – handy to test your signature check.
Use with n8n, Make and Zapier
Point the webhook at a webhook trigger of your tool (n8n "Webhook" node, Make "Custom webhook", Zapier "Catch Hook"). Examples are on the n8n, Make and Zapier pages and on the webhooks integration page.