Why Use Webhooks?
Instead of repeatedly calling the API to check if your video is done (polling), webhooks notify you instantly when processing completes.
Polling (Inefficient)
// ❌ Wastes resources, adds latency
while (true) {
const status = await client.status(jobId)
if (status.status === 'completed') break
await sleep(5000) // Check every 5 seconds
}
Webhooks (Efficient)
// ✅ Instant notification, no wasted API calls
await client.startJob(jobId, {
webhook_url: 'https://your-app.com/webhooks/videobgremover',
background: { type: 'transparent' }
})
// Server notifies you when done
Benefits:
- Real-time: Get notified instantly when processing completes
- Efficient: No repeated API calls or bandwidth waste
- Scalable: Process multiple videos without constant polling
When to use webhooks vs polling:
- ✅ Use webhooks for production apps, automation, background processing
- ⚠️ Use polling for quick scripts, testing, or if you can’t receive HTTP requests
How VideoBGRemover Webhooks Work
When you start a job with a webhook_url, our system:
- Validates your webhook URL (HTTPS required in production)
- Stores the URL with your job
- Triggers 3 events during processing:
job.started - Processing begins
job.completed - Video ready (includes output URLs)
job.failed - Processing failed (includes error message)
- Sends POST request to your URL with JSON payload
- Retries up to 3 times (10 seconds apart) if delivery fails
- Waits for BOTH background removal AND export to complete before final webhook
The system waits for your complete video (with composition/export) before sending the job.completed webhook. This prevents premature notifications.
Quick Setup
Your webhook endpoint must:
- Accept
POST requests with Content-Type: application/json
- Return a
2xx status code quickly (< 5 seconds)
- Use HTTPS (production only - HTTP allowed for testing)
Using Webhooks via cURL
Add webhook_url to your job start request:
# 1. Create job
JOB=$(curl -s -X POST https://api.videobgremover.com/v1/jobs \
-H "X-Api-Key: $API_KEY" \
-F "file=@video.mp4")
JOB_ID=$(echo $JOB | jq -r '.id')
# 2. Start job with webhook
curl -X POST https://api.videobgremover.com/v1/jobs/$JOB_ID/start \
-H "X-Api-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://your-app.com/webhooks",
"background": {
"type": "transparent",
"transparent_format": "webm_vp9"
}
}'
Response:
{
"message": "Job started",
"status": "processing",
"webhook_url": "https://your-app.com/webhooks"
}
Using Webhooks via API Client (Low-Level)
Use the VideoBGRemoverClient class for direct API control:
import { VideoBGRemoverClient } from 'videobgremover'
const client = new VideoBGRemoverClient(process.env.API_KEY!)
// Create job from file
const job = await client.createJobFile({
file: './video.mp4'
})
// Start with webhook
await client.startJob(job.id, {
webhook_url: 'https://your-app.com/webhooks',
background: {
type: 'transparent',
transparent_format: 'webm_vp9'
}
})
console.log('Job started, webhook will notify when complete')
Using Webhooks via SDK (High-Level)
Use the Video class for simpler workflows:
import { VideoBGRemoverClient, Video, RemoveBGOptions, Prefer } from 'videobgremover'
const client = new VideoBGRemoverClient(process.env.API_KEY!)
// Load video
const video = await Video.open('./video.mp4')
// Remove background with webhook
const foreground = await video.removeBackground({
client,
webhookUrl: 'https://your-app.com/webhooks',
options: new RemoveBGOptions(Prefer.WEBM_VP9)
})
console.log('Processing started, webhook will notify when complete')
Webhook support for the high-level video.removeBackground() SDK method is coming soon. For now, use the low-level client.startJob() API shown above for webhook functionality.
Webhook Events & Payloads
Your webhook endpoint will receive POST requests with these payloads:
job.started
Fires when processing begins.
{
"event": "job.started",
"timestamp": "2025-11-01T12:30:00.123Z",
"job": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "processing",
"filename": "my-video.mp4",
"created_at": "2025-11-01T12:30:00.000Z"
}
}
job.completed
Fires when BOTH background removal AND export complete.
{
"event": "job.completed",
"timestamp": "2025-11-01T12:35:00.456Z",
"job": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"export_id": "exp_webm_vp9_123",
"status": "completed",
"filename": "my-video.mp4",
"length_seconds": 30.5,
"output_format": "webm",
"background": {
"type": "transparent",
"transparent_format": "webm_vp9"
}
}
}
To download the processed video, call the job result endpoint: GET /v1/jobs/{id}/result which returns the download URL.
job.failed
Fires when processing or export fails.
{
"event": "job.failed",
"timestamp": "2025-11-01T12:32:00.789Z",
"job": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "failed",
"filename": "my-video.mp4",
"message": "Video file too large: 1250.5MB exceeds limit of 1000MB"
}
}
Webhooks include these headers:
POST /your/webhook/endpoint
Content-Type: application/json
User-Agent: VideoBGRemover-Webhooks/1.0
X-VideoBGRemover-Event: job.completed
X-VideoBGRemover-Job-ID: 550e8400-e29b-41d4-a716-446655440000
X-VideoBGRemover-Timestamp: 2025-11-01T12:35:00.456Z
X-VideoBGRemover-Attempt: 1
Checking Delivery History
View webhook delivery attempts and results:
API Endpoint
curl "https://api.videobgremover.com/v1/webhooks/deliveries?video_id=$JOB_ID" \
-H "X-Api-Key: $API_KEY"
SDK Methods
const deliveries = await client.webhookDeliveries(jobId)
console.log(`Total deliveries: ${deliveries.total_deliveries}`)
deliveries.deliveries.forEach(d => {
console.log(`${d.event_type}: ${d.delivery_status} (attempt ${d.attempt_number})`)
if (d.error_message) {
console.log(` Error: ${d.error_message}`)
}
})
Response:
{
"video_id": "550e8400-e29b-41d4-a716-446655440000",
"total_deliveries": 2,
"deliveries": [
{
"event_type": "job.started",
"webhook_url": "https://your-app.com/webhooks",
"attempt_number": 1,
"delivery_status": "delivered",
"http_status_code": 200,
"error_message": null,
"scheduled_at": "2025-11-01T12:30:00.000Z",
"delivered_at": "2025-11-01T12:30:00.234Z"
},
{
"event_type": "job.completed",
"webhook_url": "https://your-app.com/webhooks",
"attempt_number": 1,
"delivery_status": "delivered",
"http_status_code": 200,
"error_message": null,
"scheduled_at": "2025-11-01T12:35:00.000Z",
"delivered_at": "2025-11-01T12:35:00.567Z"
}
]
}
Best Practices
Process webhooks asynchronously to avoid timeouts:
// ✅ Good: Return immediately, process async
app.post('/webhooks', async (req, res) => {
res.status(200).send('OK') // Return first
// Process async
await processWebhook(req.body).catch(console.error)
})
// ❌ Bad: Slow processing blocks response
app.post('/webhooks', async (req, res) => {
await downloadVideo(req.body.job.processed_video_url) // Slow!
res.status(200).send('OK') // Timeout risk
})
Handle Retries
Use attempt_number to detect retries:
app.post('/webhooks', (req, res) => {
const { job } = req.body
const attempt = parseInt(req.headers['x-videobgremover-attempt'])
if (attempt > 1) {
console.log(`Retry attempt ${attempt} for job ${job.id}`)
}
// Process idempotently
res.status(200).send('OK')
})
Validate Events
Check event type before processing:
const VALID_EVENTS = ['job.started', 'job.completed', 'job.failed']
if (!VALID_EVENTS.includes(req.body.event)) {
return res.status(400).send('Invalid event')
}
Use HTTPS
Production webhooks require HTTPS for security.
Common Issues
Webhook Not Received
- Check delivery history using the API or SDK
- Verify HTTPS (required in production)
- Check firewall - ensure your server is publicly accessible
- Test with webhook.site to verify our system is sending correctly
Duplicate Webhooks
This is expected during retries. Implement idempotency:
const processedJobs = new Set()
app.post('/webhooks', (req, res) => {
const jobId = req.body.job.id
if (processedJobs.has(jobId)) {
return res.status(200).send('Already processed')
}
processedJobs.add(jobId)
// Process webhook...
})
Timeout Errors
- Optimize your webhook endpoint to respond in < 5 seconds
- Return 200 immediately, process asynchronously
- Check delivery history to see timeout errors
Security Note: Webhooks do not currently include HMAC signatures. Use unpredictable webhook URLs (include random tokens) and validate job IDs via the API if security is critical.
Next Steps