Webhooks
Receive real-time HTTP notifications when events occur in your VoxBurst account.
Base URL
https://api.voxburst.io/v1/webhooksEvents
| Event | Description |
|---|---|
post.created | A new post has been created (draft or scheduled) |
post.scheduled | Post has been scheduled for future publishing |
post.published | Post finished publishing — check data.status to distinguish "PUBLISHED" (all platforms succeeded) from "PARTIAL" (some platforms failed) |
post.failed | Post failed to publish on all platforms |
post.draft.approved | Post draft was approved at the final approval workflow step and is now queued for publishing |
account.connected | A new social account was connected |
account.disconnected | A social account was disconnected |
account.error | A connected account has an error (e.g. token expired or revoked) |
media.uploaded | A media file has been uploaded and passed validation |
There is no separate post.partial webhook event. When publishing results in a partial failure, VoxBurst fires post.published with status: "PARTIAL" in the payload. Always check data.status in your post.published handler to detect partial failures and trigger remediation.
Webhook Payload
All webhook events share this envelope format:
{
"id": "evt_abc123",
"type": "post.published",
"createdAt": "2026-02-20T14:00:00Z",
"data": {
"id": "post_xyz789",
"workspaceId": "ws_abc123",
"content": "Hello from VoxBurst!",
"status": "PARTIAL",
"publishedAt": "2026-02-20T14:00:00Z",
"platforms": [
{
"platform": "TWITTER",
"status": "PUBLISHED",
"platformPostId": "1234567890",
"platformPostUrl": "https://x.com/user/status/1234567890",
"publishedAt": "2026-02-20T14:00:01Z"
},
{
"platform": "INSTAGRAM",
"status": "FAILED",
"error": {
"code": "PUBLISH_ERROR",
"message": "Media format not supported: image must be JPEG or PNG"
}
}
]
}
}Use data.status to branch your handler logic: "PUBLISHED" means all platforms succeeded; "PARTIAL" means at least one platform failed and the post needs remediation via POST /v1/posts/:id/retry or POST /v1/posts/:id/platforms/:platformId/fix.
Note: platform and status values in the webhook payload are uppercase (e.g. "TWITTER", "PUBLISHED", "FAILED"). This is different from the REST API responses which return lowercase strings.
Delivery Headers
Every webhook request VoxBurst delivers includes the following headers:
| Header | Description |
|---|---|
X-VoxBurst-Signature | HMAC-SHA256 signature — see Signature Verification below |
X-VoxBurst-Timestamp | Unix timestamp of the delivery (same value as t= in the signature) |
X-VoxBurst-Delivery-Id | Unique ID for this delivery attempt — useful for deduplication |
X-VoxBurst-Event | Event type, e.g. post.published |
Content-Type | Always application/json |
Signature Verification
All webhook requests include a signature header for verification:
X-VoxBurst-Signature: t=1707753600,v2=xxxxxxxxxxxxxxxxVerify signatures to ensure requests come from VoxBurst:
import { createHmac, timingSafeEqual } from 'crypto'
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string
): boolean {
const parts = Object.fromEntries(
signature.split(',').map(p => p.split('=', 2) as [string, string])
)
const timestamp = parts['t']
const signatureValue = parts['v2']
if (!timestamp || !signatureValue) return false
// Reject requests older than 5 minutes to prevent replay attacks
const age = Math.floor(Date.now() / 1000) - parseInt(timestamp, 10)
if (age > 300) return false
const expected = createHmac('sha256', secret)
.update(`${timestamp}.${payload}`)
.digest('hex')
// Use timing-safe comparison to prevent timing attacks
try {
return timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(signatureValue, 'hex')
)
} catch {
return false
}
}
// In your webhook handler:
app.post('/webhook', (req, res) => {
const signature = req.headers['x-voxburst-signature'] as string
const isValid = verifyWebhookSignature(
req.rawBody,
signature,
process.env.WEBHOOK_SECRET!
)
if (!isValid) return res.status(401).send('Invalid signature')
const event = req.body
console.log(`Received event: ${event.type}`)
res.status(200).send('OK')
})import hmac
import hashlib
import time
def verify_webhook_signature(payload: str, signature: str, secret: str) -> bool:
parts = dict(part.split("=", 1) for part in signature.split(","))
timestamp = parts.get("t")
sig = parts.get("v2")
if not timestamp or not sig:
return False
# Reject requests older than 5 minutes to prevent replay attacks
age = int(time.time()) - int(timestamp)
if age > 300:
return False
expected = hmac.new(
secret.encode(),
f"{timestamp}.{payload}".encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(sig, expected)Always verify webhook signatures in production to prevent spoofed requests.
Requests with a timestamp older than 5 minutes (300 seconds) are automatically rejected. Ensure your server clock is synchronized via NTP.
Retry and Backoff
VoxBurst automatically retries failed webhook deliveries to ensure reliability.
Retry schedule
| Attempt | Delay before retry |
|---|---|
| 1st retry (after 1st failure) | 1 minute |
| 2nd retry (after 2nd failure) | 5 minutes |
| 3rd retry (after 3rd failure) | 15 minutes |
| After 3rd retry | Permanent failure — no more attempts |
Total time from first attempt to final failure: ~21 minutes.
Retry triggers
A delivery is retried when:
- The endpoint returns a non-2xx HTTP status code
- The request times out (see Request timeout below)
- The connection fails or returns a network error
Delivery status flow
PENDING → DELIVERING → DELIVERED (success)
↓ (non-2xx, timeout, or connection error)
PENDING (scheduled for retry)
↓ (after 3rd retry)
FAILEDRequest timeout
VoxBurst enforces a 30-second timeout per delivery attempt. If your endpoint does not respond within 30 seconds, the delivery is marked as a failure and scheduled for retry. Return 200 immediately and process the event asynchronously to avoid timeouts.
Delivery ordering
Webhook events are delivered asynchronously via a queue. No ordering is guaranteed. A post.published event may arrive before a post.scheduled event from the same workflow if the scheduled event was delayed. Always treat each event independently and use GET /v1/posts/:id to confirm current state before acting.
Duplicate deliveries
VoxBurst guarantees at-least-once delivery — your endpoint may receive the same event more than once. Use X-VoxBurst-Delivery-Id to deduplicate. Store processed delivery IDs and skip re-processing if the ID has been seen.
SSRF protection
Delivery targets must be public HTTPS endpoints. VoxBurst blocks delivery to:
- Private IP ranges (
10.x.x.x,172.16.x.x–172.31.x.x,192.168.x.x) - Loopback addresses (
127.x.x.x,::1) - Link-local addresses (
169.254.x.x)
Blocked deliveries are permanently failed — they are not retried. Use the test endpoint (POST /v1/webhooks/test) with a tunnel service like ngrok for local development.
Response body capture
VoxBurst captures up to 10 KB of your endpoint’s response body for debugging. Responses larger than 10 KB are truncated. The captured body is visible in GET /v1/webhooks/:id/deliveries/:deliveryId.
Endpoint Failure Tracking
Failed deliveries increment the endpoint’s failureCount and consecutiveFailures. After consecutive failures:
- The delivery status is marked as
failed - The endpoint statistics are updated
- At 5 consecutive failures: the workspace owner receives an email warning that the endpoint is having delivery problems.
- At 10 consecutive failures: the endpoint is automatically disabled —
enabledis set tofalseandautoDisabledAtis stamped with the current timestamp. The workspace owner receives a second email notifying them of the auto-disable.
Auto-Disable and Re-Enable
When an endpoint is auto-disabled:
enabledbecomesfalseautoDisabledAtis set to the timestamp of the disable event- VoxBurst stops delivering events to the endpoint
To re-enable a disabled endpoint, PATCH /v1/webhooks/:id with { "enabled": true }. Re-enabling also resets failure tracking: consecutiveFailures is set to 0, autoDisabledAt is cleared to null, and lastFailureAt is cleared to null. The auto-disable clock starts fresh from zero consecutive failures.
curl -X PATCH https://api.voxburst.io/v1/webhooks/wh_abc123 \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "enabled": true }'Delivery Record
Each webhook delivery creates a record with:
| Field | Description |
|---|---|
success | true if the delivery was accepted (2xx response), false otherwise |
retryCount | Number of retry attempts made (0 = first attempt succeeded or failed, no retries yet) |
statusCode | HTTP status code from the endpoint (if any) |
responseBody | Response body captured for debugging |
latencyMs | Time taken for the HTTP request in milliseconds |
error | Error message if delivery failed |
deliveredAt | Timestamp of successful delivery |
Deliveries may be retried and your endpoint should be idempotent — processing the same event multiple times should have no side effects.
Webhook Object
All webhook endpoint responses share the following fields:
| Field | Type | Description |
|---|---|---|
id | string | Unique endpoint identifier (wh_…) |
url | string | The HTTPS URL VoxBurst delivers events to |
events | string[] | Array of subscribed event types |
enabled | boolean | Whether the endpoint is active and receiving deliveries |
failureCount | number | Cumulative count of all failed deliveries (never resets) |
consecutiveFailures | number | Count of consecutive failed deliveries. Resets to 0 when the endpoint is re-enabled or a delivery succeeds. |
autoDisabledAt | string | null | ISO 8601 timestamp when the endpoint was automatically disabled after reaching 10 consecutive failures. null if not auto-disabled. |
lastDeliveryAt | string | null | ISO 8601 timestamp of the most recent successful delivery |
createdAt | string | ISO 8601 timestamp when the endpoint was created |
updatedAt | string | ISO 8601 timestamp when the endpoint was last updated |
Payload versioning
Webhook payloads are versioned via the signature scheme. The current scheme is v2 (HMAC-SHA256, t=timestamp,v2=hex). The signing string is ${timestamp}.${rawBody}.
VoxBurst will introduce a new scheme prefix (e.g. v3) if the signature algorithm changes. Old and new schemes will be sent in parallel during a transition window — your verification code should accept either scheme until you confirm migration is complete.
Payload shape changes: additive changes (new fields) are made without a version bump. Breaking changes (field removals, type changes) trigger an advance notice period and are announced in the changelog.
Operational checklist
Before going to production, verify your webhook endpoint:
- Returns
2xxwithin 30 seconds - Verifies
X-VoxBurst-Signatureon every request - Rejects requests with a timestamp older than 5 minutes (replay protection)
- Deduplicates on
X-VoxBurst-Delivery-Id - Handles
post.publishedwithdata.status: "PARTIAL"(not just"PUBLISHED") - Does not block on
post.published— callsGET /v1/posts/:idto re-confirm current state if needed - Is reachable from public IPs (no private/loopback addresses)
List Webhooks
GET /v1/webhooks
curl https://api.voxburst.io/v1/webhooks \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx"Response
{
"data": [
{
"id": "wh_abc123",
"url": "https://yourapp.com/webhooks/voxburst",
"events": ["post.published", "post.failed"],
"enabled": true,
"failureCount": 0,
"consecutiveFailures": 0,
"autoDisabledAt": null,
"lastDeliveryAt": null,
"createdAt": "2026-02-20T10:00:00Z",
"updatedAt": "2026-02-20T10:00:00Z"
}
]
}Create Webhook
POST /v1/webhooks
Returns HTTP 201 Created.
Required scopes: webhooks:write
Required feature: custom_integrations (Pro plan or higher)
curl -X POST https://api.voxburst.io/v1/webhooks \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/voxburst",
"events": ["post.published", "post.failed"]
}'VoxBurst generates a unique signing secret for each webhook endpoint automatically. The secret is returned once in the creation response — copy it immediately and store it securely. It cannot be retrieved again.
Response
{
"id": "wh_abc123",
"url": "https://yourapp.com/webhooks/voxburst",
"events": ["post.published", "post.failed"],
"enabled": true,
"failureCount": 0,
"consecutiveFailures": 0,
"autoDisabledAt": null,
"lastDeliveryAt": null,
"createdAt": "2026-02-20T10:00:00Z",
"updatedAt": "2026-02-20T10:00:00Z",
"secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}Rotate Webhook Secret
POST /v1/webhooks/:id/rotate-secret
Rotate the signing secret for a webhook endpoint. The old secret becomes invalid immediately. The new secret is returned once in the response — copy and store it securely.
Required scopes: webhooks:write
curl -X POST https://api.voxburst.io/v1/webhooks/wh_abc123/rotate-secret \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx"Response
{
"success": true,
"secret": "whsec_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"message": "Secret rotated successfully. Store the new secret securely - it will not be shown again."
}Get Webhook
GET /v1/webhooks/:id
curl https://api.voxburst.io/v1/webhooks/wh_abc123 \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx"Update Webhook
PATCH /v1/webhooks/:id
Update webhook URL, events, or status.
Required scopes: webhooks:write
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
url | string | No | New HTTPS delivery URL |
events | string[] | No | Replacement list of subscribed event types |
enabled | boolean | No | Enable or disable the endpoint |
curl -X PATCH https://api.voxburst.io/v1/webhooks/wh_abc123 \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"events": ["post.published", "post.failed", "account.error"]
}'Setting enabled: true resets failure tracking so the auto-disable clock starts fresh: consecutiveFailures is set to 0, autoDisabledAt is cleared to null, and lastFailureAt is cleared to null. This applies whether the endpoint was auto-disabled or manually disabled.
Test Webhook
POST /v1/webhooks/test
Send a test event to a webhook endpoint to verify it is reachable and responding correctly.
Required scopes: webhooks:write
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
webhookId | string | Yes | ID of the webhook endpoint to test |
curl -X POST https://api.voxburst.io/v1/webhooks/test \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "webhookId": "wh_abc123" }'Delete Webhook
DELETE /v1/webhooks/:id
Required scopes: webhooks:write
curl -X DELETE https://api.voxburst.io/v1/webhooks/wh_abc123 \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx"List Deliveries
GET /v1/webhooks/:id/deliveries
List delivery attempts for a webhook endpoint.
curl https://api.voxburst.io/v1/webhooks/wh_abc123/deliveries \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx"Response
{
"data": [
{
"id": "del_xyz789",
"webhookId": "wh_abc123",
"eventType": "post.published",
"url": "https://yourapp.com/webhooks/voxburst",
"success": true,
"retryCount": 0,
"statusCode": 200,
"latencyMs": 143,
"responseBody": null,
"error": null,
"createdAt": "2026-04-09T12:00:05Z",
"deliveredAt": "2026-04-09T12:00:05Z"
}
],
"pagination": { "hasMore": false }
}Get Delivery Stats
GET /v1/webhooks/:id/deliveries/stats
Get aggregate delivery statistics for a webhook endpoint.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
since | string | ISO 8601 date — only include deliveries after this timestamp |
curl "https://api.voxburst.io/v1/webhooks/wh_abc123/deliveries/stats?since=2026-04-01T00:00:00Z" \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx"Response
{
"data": {
"total": 142,
"delivered": 139,
"failed": 3,
"successRate": 97.9
}
}Get Delivery
GET /v1/webhooks/:id/deliveries/:deliveryId
Get the full detail for a single delivery attempt, including request and response bodies.
curl https://api.voxburst.io/v1/webhooks/wh_abc123/deliveries/del_xyz789 \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx"Retry Delivery
POST /v1/webhooks/:id/deliveries/:deliveryId/retry
Manually re-deliver a specific event to the webhook endpoint.
Required scopes: webhooks:write
curl -X POST https://api.voxburst.io/v1/webhooks/wh_abc123/deliveries/del_xyz789/retry \
-H "Authorization: Bearer vb_live_xxxxxxxxxxxxx"