Getting Started
Webhooks allow your application to receive real-time HTTP POST notifications when events happen in your Viraly workspace. Instead of polling the API for changes, webhooks push data to your server the moment something happens.
Setup in 3 Steps
- 1Go to Settings → Webhooks and click Add Endpoint.
- 2Enter your HTTPS endpoint URL and select which events to receive. Copy the signing secret — it's shown only once.
- 3Implement an HTTP POST handler on your server. Verify the signature, parse the JSON payload, and return a 2xx status.
Webhooks are available on the Agency and Enterprise plans. Each workspace can configure up to 5 webhook endpoints.
Payload Format
Every webhook sends a consistent JSON envelope. The data field varies by event type.
{
"id": "wh_a8Kf3mN9p",
"event": "post.published",
"created_at": "2026-04-14T12:00:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"...": "event-specific payload"
}
}Headers Included
X-Viraly-Signature— HMAC-SHA256 signatureX-Viraly-Timestamp— Unix timestampX-Viraly-Event— Event typeX-Viraly-Delivery-Id— Unique delivery ID
Response Requirements
- Return any 2xx status to acknowledge
- Respond within 10 seconds
- Non-2xx or timeout triggers a retry
- Idempotency recommended using
idfield
Signature Verification
Every webhook is signed with HMAC-SHA256 using the signing secret you received when creating the endpoint. Always verify the signature before processing.
const crypto = require('crypto');
function verifyWebhook(secret, signature, timestamp, body) {
// Reject requests older than 5 minutes (replay protection)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp)) > 300) {
return false;
}
const signedContent = timestamp + '.' + body;
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(signedContent)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// In your webhook handler:
app.post('/webhooks', (req, res) => {
const isValid = verifyWebhook(
process.env.VIRALY_WEBHOOK_SECRET,
req.headers['x-viraly-signature'],
req.headers['x-viraly-timestamp'],
req.body // raw body string
);
if (!isValid) return res.status(401).send('Invalid signature');
const event = JSON.parse(req.body);
console.log('Received:', event.event, event.data);
res.status(200).send('OK');
});Retry Policy
If your endpoint returns a non-2xx status or times out, Viraly retries delivery with exponential backoff.
| Attempt | Delay | On Failure |
|---|---|---|
| 1st attempt | Immediate | Retry after 60 seconds |
| 2nd attempt | ~60 seconds | Retry after 5 minutes |
| 3rd attempt | ~5 minutes | Marked as failed |
Failed deliveries are visible in your webhook delivery log for 7 days.
Event Reference
Posts
Events related to social media post lifecycle.
| Method | Event Type |
|---|---|
| POST | post.created |
| POST | post.updated |
| POST | post.scheduled |
| POST | post.published |
| POST | post.failed |
| POST | post.deleted |
| POST | post.rescheduled |
post.createdFired when a new post is created (draft, scheduled, or publish-now).
{
"id": "wh_a8Kf3mN9p",
"event": "post.created",
"created_at": "2026-04-14T12:00:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"id": "abc12345",
"title": "Summer sale announcement",
"status": "Scheduled",
"scheduled_at": "2026-04-20T14:00:00Z",
"channel_id": "ch123456",
"channel_type": "Instagram"
}
}post.updatedFired when an existing post is modified (caption, schedule, attachments).
{
"id": "wh_b9Lg4nO0q",
"event": "post.updated",
"created_at": "2026-04-14T12:30:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"id": "abc12345",
"title": "Summer sale — updated!",
"status": "Scheduled",
"scheduled_at": "2026-04-21T10:00:00Z",
"channel_id": "ch123456",
"channel_type": "Instagram"
}
}post.scheduledFired when a post transitions to Scheduled status.
{
"id": "wh_c0Mh5oP1r",
"event": "post.scheduled",
"created_at": "2026-04-14T12:00:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"id": "abc12345",
"title": "Summer sale announcement",
"scheduled_at": "2026-04-20T14:00:00Z",
"channel_id": "ch123456",
"channel_type": "Instagram"
}
}post.publishedFired when a post is successfully published to the social platform.
{
"id": "wh_d1Ni6pQ2s",
"event": "post.published",
"created_at": "2026-04-20T14:00:15Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"notification_id": "ntf12345",
"message": "Your post has been published to Instagram.",
"details": {
"post_id": "abc12345",
"channel_name": "My Instagram",
"external_url": "https://instagram.com/p/ABC123"
}
}
}post.failedFired when a post fails to publish (processing or publishing error).
{
"id": "wh_e2Oj7qR3t",
"event": "post.failed",
"created_at": "2026-04-20T14:00:15Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"notification_id": "ntf12346",
"message": "Failed to publish post to Instagram.",
"details": {
"post_id": "abc12345",
"channel_name": "My Instagram",
"error": "Instagram token expired. Please reconnect."
}
}
}post.deletedFired when a post is deleted.
{
"id": "wh_f3Pk8rS4u",
"event": "post.deleted",
"created_at": "2026-04-14T15:00:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"id": "abc12345",
"channel_id": "ch123456"
}
}post.rescheduledFired when a post's scheduled time is changed.
{
"id": "wh_g4Ql9sT5v",
"event": "post.rescheduled",
"created_at": "2026-04-14T16:00:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"id": "abc12345",
"title": "Summer sale announcement",
"status": "Scheduled",
"scheduled_at": "2026-04-22T09:00:00Z",
"channel_id": "ch123456"
}
}Approvals
Events related to the post approval workflow.
| Method | Event Type |
|---|---|
| POST | post.approval_requested |
| POST | post.approved |
| POST | post.rejected |
| POST | post.approval_cancelled |
post.approval_requestedFired when a post is sent for approval.
{
"id": "wh_h5Rm0tU6w",
"event": "post.approval_requested",
"created_at": "2026-04-14T10:00:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"id": "abc12345",
"title": "Summer sale announcement",
"status": "PendingApproval",
"channel_id": "ch123456",
"approvers": [
"usr001",
"usr002"
]
}
}post.approvedFired when a post meets its approval threshold and transitions to Scheduled.
{
"id": "wh_i6Sn1uV7x",
"event": "post.approved",
"created_at": "2026-04-14T11:00:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"id": "abc12345",
"title": "Summer sale announcement",
"status": "Scheduled",
"approver": {
"id": "usr001",
"name": "Jane Doe"
}
}
}post.rejectedFired when a post is rejected and reverts to Draft.
{
"id": "wh_j7To2vW8y",
"event": "post.rejected",
"created_at": "2026-04-14T11:30:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"id": "abc12345",
"title": "Summer sale announcement",
"status": "Draft",
"approver": {
"id": "usr002",
"name": "John Smith"
}
}
}post.approval_cancelledFired when an approval request is withdrawn by the post creator or workspace owner.
{
"id": "wh_k8Up3wX9z",
"event": "post.approval_cancelled",
"created_at": "2026-04-14T12:00:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"id": "abc12345",
"title": "Summer sale announcement",
"status": "Draft"
}
}Channels
Events related to connected social media accounts.
| Method | Event Type |
|---|---|
| POST | channel.connected |
| POST | channel.disconnected |
| POST | channel.updated |
channel.connectedFired when a new social media account is connected.
{
"id": "wh_l9Vq4xY0a",
"event": "channel.connected",
"created_at": "2026-04-14T09:00:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"id": "ch123456",
"name": "My Instagram",
"type": "Instagram",
"username": "@mybrand"
}
}channel.disconnectedFired when a social media account is removed.
{
"id": "wh_m0Wr5yZ1b",
"event": "channel.disconnected",
"created_at": "2026-04-14T09:30:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"id": "ch123456",
"name": "My Instagram",
"type": "Instagram"
}
}channel.updatedFired when a channel's settings are modified.
{
"id": "wh_n1Xs6zA2c",
"event": "channel.updated",
"created_at": "2026-04-14T10:00:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"id": "ch123456",
"name": "My Instagram (Rebranded)",
"type": "Instagram"
}
}Bio Links
Events related to bio link pages and subscribers.
| Method | Event Type |
|---|---|
| POST | biolink.created |
| POST | biolink.updated |
| POST | biolink.deleted |
| POST | biolink.subscriber.created |
biolink.createdFired when a new bio link page is created.
{
"id": "wh_o2Yt7AB3d",
"event": "biolink.created",
"created_at": "2026-04-14T08:00:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"id": "bl123456",
"title": "My Link Page",
"slug": "mybrand"
}
}biolink.updatedFired when a bio link page is modified.
{
"id": "wh_p3Zu8BC4e",
"event": "biolink.updated",
"created_at": "2026-04-14T08:30:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"id": "bl123456",
"title": "My Link Page — Updated",
"slug": "mybrand"
}
}biolink.deletedFired when a bio link page is deleted.
{
"id": "wh_q4Av9CD5f",
"event": "biolink.deleted",
"created_at": "2026-04-14T09:00:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"id": "bl123456",
"slug": "mybrand"
}
}biolink.subscriber.createdFired when someone subscribes via a bio link page email collection form.
{
"id": "wh_r5Bw0DE6g",
"event": "biolink.subscriber.created",
"created_at": "2026-04-14T10:00:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"id": "sub12345",
"biolink_id": "bl123456",
"subscribed_at": "2026-04-14T10:00:00Z"
}
}Team
Events related to workspace team members.
| Method | Event Type |
|---|---|
| POST | team.member_invited |
| POST | team.member_joined |
| POST | team.member_removed |
| POST | team.member_role_changed |
team.member_invitedFired when a team member invitation is sent.
{
"id": "wh_s6Cx1EF7h",
"event": "team.member_invited",
"created_at": "2026-04-14T07:00:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"id": "usr003",
"role": "Member"
}
}team.member_joinedFired when an invited team member accepts and joins the workspace.
{
"id": "wh_t7Dy2FG8i",
"event": "team.member_joined",
"created_at": "2026-04-14T07:30:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"id": "usr003",
"name": "Alex Johnson",
"role": "Member"
}
}team.member_removedFired when a team member is removed from the workspace.
{
"id": "wh_u8Ez3GH9j",
"event": "team.member_removed",
"created_at": "2026-04-14T08:00:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"id": "usr003"
}
}team.member_role_changedFired when a team member's role or permissions are updated.
{
"id": "wh_v9Fa4HI0k",
"event": "team.member_role_changed",
"created_at": "2026-04-14T08:30:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"id": "usr003",
"role": "Owner"
}
}Workspace
Events related to workspace-level changes.
| Method | Event Type |
|---|---|
| POST | workspace.plan_changed |
workspace.plan_changedFired when the workspace subscription plan changes (upgrade, downgrade, or cancellation).
{
"id": "wh_w0Gb5IJ1l",
"event": "workspace.plan_changed",
"created_at": "2026-04-14T06:00:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"old_plan": "Business",
"new_plan": "Agency"
}
}Content
Events related to content creation tools.
| Method | Event Type |
|---|---|
| POST | idea.created |
| POST | media.uploaded |
idea.createdFired when a new content idea is saved to an idea board.
{
"id": "wh_x1Hc6JK2m",
"event": "idea.created",
"created_at": "2026-04-14T05:00:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"id": "idea12345",
"title": "Behind the scenes video",
"board_id": "board001"
}
}media.uploadedFired when a new media file is uploaded to the media library.
{
"id": "wh_y2Id7KL3n",
"event": "media.uploaded",
"created_at": "2026-04-14T05:30:00Z",
"workspace": {
"id": "YNFoJ08S",
"name": "My Brand"
},
"data": {
"id": "att12345",
"file_name": "summer-banner.png",
"mime_type": "image/png",
"url": "https://cdn.viraly.io/media/summer-banner.png"
}
}