Skip to Content
GuidesAdvanced workflows

Advanced workflows

Copy-paste recipes for scenarios that go beyond basic post creation.


OAuth account connect + callback

Connect a new social account via OAuth. Two steps: initiate (get the authorization URL) → handle the callback (exchange the code for a connected account).

import { VoxBurstClient } from '@voxburst/sdk' import express from 'express' const client = new VoxBurstClient({ apiKey: process.env.VOXBURST_API_KEY! }) const app = express() // Step 1 — initiate: redirect the user to the platform's OAuth page app.get('/connect/:platform', async (req, res) => { const platform = req.params.platform.toUpperCase() // e.g. 'TWITTER', 'LINKEDIN' const callbackUrl = `${process.env.APP_URL}/oauth/callback/${platform.toLowerCase()}` const { url } = await client.accounts.initiateConnect(platform, callbackUrl) res.redirect(url) }) // Step 2 — callback: exchange code via REST (completeConnect is not in the TypeScript SDK) app.get('/oauth/callback/:platform', async (req, res) => { const { code, state } = req.query as { code: string; state: string } const platform = req.params.platform.toLowerCase() try { const r = await fetch(`https://api.voxburst.io/v1/accounts/callback/${platform}`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.VOXBURST_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ code, state }), }) if (!r.ok) throw new Error(await r.text()) const account = await r.json() console.log(`Connected: ${account.platform} — @${account.username} (${account.id})`) res.redirect('/settings/accounts?connected=true') } catch (err) { console.error('OAuth callback failed:', err) res.redirect('/settings/accounts?error=oauth_failed') } })

The state parameter is required — it prevents CSRF. VoxBurst generates and validates it automatically. Do not modify or discard it between Step 1 and Step 2.

REST equivalent (curl):

# Step 1 — get authorization URL curl -X POST https://api.voxburst.io/v1/accounts/connect/twitter \ -H "Authorization: Bearer $VOXBURST_API_KEY" \ -H "Content-Type: application/json" \ -d '{"callbackUrl": "https://yourapp.com/oauth/callback/twitter"}' # → { "url": "https://twitter.com/i/oauth2/authorize?..." } # Step 2 — complete (after the user authorizes and is redirected back) curl -X POST https://api.voxburst.io/v1/accounts/callback/twitter \ -H "Authorization: Bearer $VOXBURST_API_KEY" \ -H "Content-Type: application/json" \ -d '{"code": "...", "state": "..."}' # → Account object

Webhook HMAC signature verification

Every webhook delivery includes an X-VoxBurst-Signature header — a hex-encoded HMAC-SHA256 of the raw request body using your endpoint’s signing secret.

import { createHmac, timingSafeEqual } from 'crypto' import express from 'express' const app = express() // Important: use raw body middleware (not JSON) — the signature is over raw bytes app.use('/webhooks/voxburst', express.raw({ type: 'application/json' })) app.post('/webhooks/voxburst', (req, res) => { const body = req.body as Buffer const sig = req.headers['x-voxburst-signature'] as string if (!verifyWebhookSignature(body, sig, process.env.VOXBURST_WEBHOOK_SECRET!)) { return res.status(401).json({ error: 'Invalid signature' }) } // Ack immediately — process async to stay under the 10s delivery timeout res.sendStatus(200) const event = JSON.parse(body.toString()) handleEvent(event).catch(err => console.error('Event handler error:', err)) }) function verifyWebhookSignature(body: Buffer, sig: string, secret: string): boolean { const expected = createHmac('sha256', secret).update(body).digest('hex') try { return timingSafeEqual(Buffer.from(sig, 'hex'), Buffer.from(expected, 'hex')) } catch { return false // length mismatch — definitely invalid } } async function handleEvent(event: any) { switch (event.type) { case 'post.published': console.log(`✅ ${event.data.id} published to ${event.data.platforms.length} platform(s)`) break case 'post.failed': console.error(`❌ ${event.data.id} failed`) for (const p of event.data.platforms.filter((p: any) => p.status === 'FAILED')) { console.error(` ${p.platform}: ${p.error?.message}`) } break case 'post.partial': console.warn(`⚠️ ${event.data.id} partial — some platforms failed`) break case 'account.connected': console.log(`🔗 New account connected: ${event.data.platform} @${event.data.username}`) break } }

The signing secret is shown only once — at webhook creation. Store it in a secret manager immediately. If lost, rotate it via PATCH /v1/webhooks/:id/rotate-secret.

Deduplication: webhook deliveries may retry on timeout. Use event.id as an idempotency key in your handler:

const processed = new Set<string>() // use Redis/DB in production async function handleEvent(event: any) { if (processed.has(event.id)) return // already handled processed.add(event.id) // ... handle event }

Partial publish failure → retry or fix

When a post’s status is partial, some platforms published and some failed. Use fixPlatform to resubmit only the failed destinations.

async function recoverPartialPost(postId: string) { const post = await client.posts.get(postId) if (post.status === 'failed') { // All platforms failed — retry the whole post console.log(`Retrying all platforms for ${postId}`) return client.posts.retry(postId) } if (post.status === 'partial') { const failed = post.platforms.filter(p => p.status === 'failed') console.log(`Fixing ${failed.length} failed platform(s) on ${postId}`) for (const platform of failed) { const errorCode = platform.error?.code if (errorCode === 'RATE_LIMITED' || errorCode === 'PLATFORM_UNAVAILABLE') { // Transient — fix without content changes // await client.posts.fixPlatform(postId, platform.postPlatformId) } else if (errorCode === 'CONTENT_TOO_LONG' || errorCode === 'MEDIA_REQUIRED') { // Content error — fix with an override for this platform await fetch(, { method: 'POST', headers: { Authorization: , 'Content-Type': 'application/json' }, body: JSON.stringify({ content: truncateForPlatform(post.content, platform.platform) }), }) } else { console.error(`Unrecoverable error on ${platform.platform}: ${platform.error?.message}`) } } } } function truncateForPlatform(content: string, platform: string): string { const limits: Record<string, number> = { twitter: 280, bluesky: 300, threads: 500 } const limit = limits[platform] return limit && content.length > limit ? content.slice(0, limit - 3) + '...' : content }

Recurring posts

Create a post that repeats on a schedule. The post object gets isRecurring: true and generates child posts on each recurrence.

// Create a recurring post (weekly on Monday at 9am UTC) const recurring = await client.posts.create({ content: 'Weekly reminder: check your analytics dashboard 📊', accountIds: ['acc_twitter_abc', 'acc_linkedin_xyz'], scheduledFor: '2026-07-07T09:00:00Z', // first occurrence recurrence: { frequency: 'WEEKLY', interval: 1, byDay: ['MO'], // Monday until: '2026-12-31T00:00:00Z', // stop after this date; // REST-only — use raw fetch omit for indefinite }, }) console.log(`Recurring post created: ${recurring.id} (isRecurring: ${recurring.isRecurring})`) // List all posts generated by this recurring rule const children = [] for await (const post of client.posts.listAll({ recurringPostId: recurring.id })) { children.push(post) } console.log(`Generated ${children.length} scheduled posts`) // Update the recurrence rule (affects future occurrences only) await client.posts.update(recurring.id, { recurrence: { frequency: 'WEEKLY', interval: 2, // change to every two weeks byDay: ['MO'], }, }) // Cancel a single occurrence without affecting others const oneOff = children[2] await client.posts.cancel(oneOff.id) // Cancel the entire series await client.posts.cancel(recurring.id)

LinkedIn / Facebook page selection

LinkedIn and Facebook accounts may have multiple associated pages. List available pages and select one before posting.

// List pages via REST (listPages is not in the TypeScript SDK) const pagesRes = await fetch('https://api.voxburst.io/v1/accounts/acc_linkedin_abc/pages', { headers: { Authorization: `Bearer ${process.env.VOXBURST_API_KEY}` }, }) const { data: pages } = await pagesRes.json() // pages: [{ pageId: 'page_123', name: 'Acme Corp', followerCount: 12400 }, ...] // Select a default page via REST (selectPage is not in the TypeScript SDK) await fetch('https://api.voxburst.io/v1/accounts/acc_linkedin_abc/select-page', { method: 'POST', headers: { Authorization: `Bearer ${process.env.VOXBURST_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ pageId: pages[0].pageId }), }) // Post via SDK — page was set above const post = await client.posts.create({ content: 'Announcing our new feature!', accountIds: ['acc_linkedin_abc'], })

REST equivalent:

# List pages curl "https://api.voxburst.io/v1/accounts/acc_linkedin_abc/pages" \ -H "Authorization: Bearer $VOXBURST_API_KEY" # Select a default page curl -X POST "https://api.voxburst.io/v1/accounts/acc_linkedin_abc/select-page" \ -H "Authorization: Bearer $VOXBURST_API_KEY" \ -H "Content-Type: application/json" \ -d '{"pageId": "page_123"}'

YouTube playlist targeting

When posting a video to YouTube, optionally assign it to a playlist.

// List playlists via REST (listPlaylists is not in the TypeScript SDK) const plRes = await fetch('https://api.voxburst.io/v1/accounts/acc_youtube_abc/playlists', { headers: { Authorization: `Bearer ${process.env.VOXBURST_API_KEY}` }, }) const { data: playlists } = await plRes.json() // playlists: [{ playlistId: 'PLabc123', title: 'Product Demos', videoCount: 8 }, ...] // Assign the video to a playlist via platformOverrides const post = await client.posts.create({ content: 'How to get started with VoxBurst in 5 minutes', accountIds: ['acc_youtube_abc'], contentType: 'VIDEO', media: ['media_videoabc123'], platformOverrides: { YOUTUBE: { playlistId: playlists[0].playlistId, title: 'Getting Started with VoxBurst', // YouTube video title description: 'Full walkthrough of the core features.', visibility: 'PUBLIC', // 'PUBLIC' | 'PRIVATE' | 'UNLISTED' categoryId: '28', // YouTube category ID (28 = Science & Technology) }, }, })

REST equivalent:

# List playlists curl "https://api.voxburst.io/v1/accounts/acc_youtube_abc/playlists" \ -H "Authorization: Bearer $VOXBURST_API_KEY"

Last updated on