Skip to main content

What are Webhooks?

Webhooks allow you to receive real-time notifications when your video generation or export jobs complete. Instead of polling the status endpoint repeatedly, Hoox will send an HTTP POST request to your specified URL when the job finishes.

Benefits

Real-time Updates

Get notified immediately when jobs complete, no need to poll

Reduced API Calls

Save on rate limits by eliminating status polling

Better UX

Provide instant feedback to your users

Automation

Trigger downstream processes automatically

How Webhooks Work

1

Configure Webhook URL

Include a webhook_url in your generation or export request
2

Job Processing

Hoox processes your video generation or export job
3

Webhook Delivery

When the job completes (success or failure), Hoox sends a POST request to your URL
4

Acknowledgment

Your endpoint should respond with a 200 status code to confirm receipt

Webhook Payload

All webhooks include a task field to identify the type of operation. This helps you route and process webhooks correctly.

Payload Structure by Task Type

Fieldvideo_generatevideo_exportavatar_createavatar_edit
task
job_id
avatar_id
look_id
status
result
error✅ (on failure)✅ (on failure)✅ (on failure)✅ (on failure)
Key differences:
  • Video operations use job_id (the Trigger.dev run ID)
  • Avatar operations use avatar_id and look_id instead
  • Always check the task field to determine how to process the webhook

Successful Generation

{
  "task": "video_generate",
  "job_id": "run_01234567890abcdef",
  "status": "completed",
  "result": {
    "video_id": "video_abcdef1234567890",
    "thumbnail_url": "https://cdn.hoox.video/thumbnails/video_123.jpg",
    "cost": 5,
    "created_at": "2024-01-15T10:30:00Z"
  }
}

Failed Generation

{
  "task": "video_generate",
  "job_id": "run_01234567890abcdef",
  "status": "failed",
  "error": {
    "code": "INSUFFICIENT_CREDITS",
    "message": "Not enough credits to complete video generation"
  }
}

Setting Up Webhooks

1. Create an Endpoint

Your webhook endpoint should:
  • Accept POST requests
  • Respond with 200 status code
  • Process the payload asynchronously if needed
app.post('/webhook/hoox', express.json(), (req, res) => {
  const { task, job_id, avatar_id, look_id, status, result, error } = req.body;
  
  console.log(`Webhook received for ${task}: ${status}`);
  
  // Route based on task type
  switch (task) {
    case 'video_generate':
      handleVideoGeneration(job_id, status, result, error);
      break;
    case 'video_export':
      handleVideoExport(job_id, status, result, error);
      break;
    case 'avatar_create':
    case 'avatar_edit':
      handleAvatar(avatar_id, look_id, status, result, error);
      break;
    default:
      console.warn('Unknown task type:', task);
  }
  
  // Always respond with 200
  res.status(200).send('OK');
});

function handleVideoGeneration(job_id, status, result, error) {
  if (status === 'completed') {
    console.log('Video generated:', result.video_id);
    updateJobStatus(job_id, 'completed', result);
    notifyUser(job_id, result);
  } else {
    console.error('Generation failed:', error);
    updateJobStatus(job_id, 'failed', null, error);
    notifyUserOfFailure(job_id, error);
  }
}

function handleVideoExport(job_id, status, result, error) {
  if (status === 'completed') {
    console.log('Video exported:', result.download_url);
    updateJobStatus(job_id, 'completed', result);
    notifyUser(job_id, result);
  } else {
    console.error('Export failed:', error);
    updateJobStatus(job_id, 'failed', null, error);
    notifyUserOfFailure(job_id, error);
  }
}

function handleAvatar(avatar_id, look_id, status, result, error) {
  if (status === 'completed') {
    console.log('Avatar ready:', avatar_id, look_id);
    updateAvatarStatus(avatar_id, look_id, 'completed', result);
    notifyUser(avatar_id, result);
  } else {
    console.error('Avatar failed:', error);
    updateAvatarStatus(avatar_id, look_id, 'failed', null, error);
    notifyUserOfFailure(avatar_id, error);
  }
}

2. Use in API Calls

Include your webhook URL when starting jobs:
const response = await fetch('https://app.hoox.video/api/public/v1/generation/start', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer hx_live_your_api_key_here',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    prompt: "Create a video about space exploration",
    voice_id: "en-US-JennyNeural",
    webhook_url: "https://your-app.com/webhook/hoox"
  })
});

// Will receive webhook with task: "video_generate"

Security Considerations

1. Validate Requests

Verify that webhook requests are coming from Hoox:
// Check User-Agent header
const userAgent = req.headers['user-agent'];
if (!userAgent || !userAgent.includes('Hoox-API')) {
  return res.status(401).send('Unauthorized');
}

// Check for Hoox webhook header
const isHooxWebhook = req.headers['x-hoox-webhook'];
if (isHooxWebhook !== 'true') {
  return res.status(401).send('Unauthorized');
}

2. Use HTTPS

Always use HTTPS URLs for your webhook endpoints to ensure data is encrypted in transit.

3. Implement Idempotency

Handle duplicate webhook deliveries gracefully:
const processedJobs = new Set();

app.post('/webhook/hoox', (req, res) => {
  const { task, job_id, avatar_id, look_id } = req.body;
  
  // Create unique ID based on task type
  const uniqueId = task === 'avatar_create' || task === 'avatar_edit' 
    ? `${avatar_id}:${look_id}` 
    : job_id;
  
  // Check if we've already processed this webhook
  if (processedJobs.has(uniqueId)) {
    console.log(`Duplicate webhook for ${uniqueId}, ignoring`);
    return res.status(200).send('OK');
  }
  
  // Process the webhook
  processWebhook(req.body);
  
  // Mark as processed
  processedJobs.add(uniqueId);
  
  res.status(200).send('OK');
});

Retry Behavior

Hoox will retry webhook deliveries if your endpoint:
  • Returns a non-2xx status code
  • Times out (after 10 seconds)
  • Is unreachable
Retry Schedule:
  • 1st retry: After 1 second
  • 2nd retry: After 2 seconds
  • 3rd retry: After 4 seconds
  • Maximum: 3 retry attempts
If all retries fail, the webhook will be discarded. Ensure your endpoint is reliable and responds quickly.

Testing Webhooks

1. Use ngrok for Local Development

# Install ngrok
npm install -g ngrok

# Expose your local server
ngrok http 3000

# Use the HTTPS URL in your webhook_url
# https://abc123.ngrok.io/webhook/hoox

2. Webhook Testing Tools

  • RequestBin: Create temporary endpoints to inspect payloads
  • Webhook.site: Test and debug webhook deliveries
  • Postman: Mock webhook requests for testing

3. Test Endpoint

Create a simple test endpoint to verify webhook delivery:
app.post('/webhook/test', (req, res) => {
  console.log('Webhook Headers:', req.headers);
  console.log('Webhook Body:', JSON.stringify(req.body, null, 2));
  console.log('Task Type:', req.body.task);
  
  // Log different IDs based on task type
  if (req.body.task === 'avatar_create' || req.body.task === 'avatar_edit') {
    console.log('Avatar ID:', req.body.avatar_id);
    console.log('Look ID:', req.body.look_id);
  } else {
    console.log('Job ID:', req.body.job_id);
  }
  
  res.status(200).send('Webhook received successfully');
});

Best Practices

  • Always check the task field first to route webhooks correctly
  • Use different handlers for different task types
  • Validate that required fields exist based on task type
  • For avatars: use avatar_id and look_id
  • For videos: use job_id
  • Respond with 200 status as quickly as possible
  • Process heavy operations asynchronously
  • Use queues for complex workflows
  • Log all webhook deliveries for debugging
  • Handle malformed payloads gracefully
  • Implement monitoring and alerting
  • Check for required fields based on task type
  • Use load balancers for high-volume webhooks
  • Implement rate limiting on your endpoint
  • Consider using message queues for processing

Common Issues

IssueCauseSolution
Webhooks not receivedEndpoint unreachableCheck URL and firewall settings
Duplicate processingNo idempotency checkImplement unique ID tracking (job_id or avatar_id:look_id)
Slow processingHeavy operations in handlerMove to background jobs
Missing webhooksEndpoint returning errorsFix endpoint logic and status codes
Wrong task routingNot checking task fieldAlways check the task field first
Missing IDsAccessing wrong fieldsUse job_id for videos, avatar_id/look_id for avatars
Always test your webhook endpoints thoroughly before using them in production.