Getting Started

To start using webhooks, you’ll need to:
  1. Set up an endpoint - Create a URL that can receive POST requests
  2. Configure the webhook - Add your endpoint in the Consumer App Portal
  3. Verify signatures - Implement signature verification for security
  4. Handle events - Process the incoming webhook data

Adding Endpoints

Animus uses Svix for processing webhook events. You will be given a portal link by the Animus team where you can use this portal to set up webhook endpoints and subscribe to events. In order to start listening to messages, you will need to configure your endpoints. To add an endpoint, select the “Endpoints” tab from the left sidebar. From there you can add a new endpoint and set the events you want to be notified about. If you don’t specify any event types, by default, your endpoint will receive all events, regardless of type. You can also edit any existing endpoints. You’ll be able to view and inspect webhooks sent to your Svix Play URL, making it effortless to get started. Adding a webhook endpoint

Events

The following is a list of events you can subscribe to. Webhook events selection

Available Event Types

  • media.completed - A video processing job has finished successfully or with a failure.

Event Payload Structure

When a webhook event is triggered, you’ll receive a POST request to your configured endpoint with a JSON payload containing:
{
  "event_type": "media.completed",
  "timestamp": "2024-01-15T10:30:00Z",
  "data": {
    "job_id": "uuid-string",
    "status": "completed",
    "media_url": "https://example.com/video.mp4",
    "results": {
      // Processing results based on the job type
    }
  }
}

Verifying Webhook Signatures

Webhook signatures let you verify that webhook messages are actually sent by us and not a malicious actor. For a more detailed explanation, check out this article on why you should verify webhooks. Our webhook partner Svix offers a set of useful libraries that make verifying webhooks very simple:
import { Webhook } from "svix";

const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";

// These were all sent from the server
const headers = {
  "svix-id": "msg_p5jXN8AQM9LWM0D4loKWxJek",
  "svix-timestamp": "1614265330",
  "svix-signature": "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=",
};
const payload = '{"test": 2432232314}';

const wh = new Webhook(secret);
// Throws on error, returns the verified content on success
const verifiedPayload = wh.verify(payload, headers);
For detailed information on how to use the Svix portal to manage your webhooks, refer to the Svix documentation.
For more instructions and examples of how to verify signatures, check out their webhook verification documentation.

Complete Webhook Handler Examples

Here are complete examples of webhook handlers in different frameworks:
import express from 'express';
import { Webhook } from 'svix';

const app = express();
const webhookSecret = process.env.WEBHOOK_SECRET; // Your webhook secret

// Middleware to capture raw body for signature verification
app.use('/webhook', express.raw({ type: 'application/json' }));

app.post('/webhook', (req, res) => {
  const payload = req.body;
  const headers = req.headers;

  // Verify the webhook signature
  const wh = new Webhook(webhookSecret);
  let event;

  try {
    event = wh.verify(payload, headers);
  } catch (err) {
    console.error('Webhook signature verification failed:', err.message);
    return res.status(400).send('Invalid signature');
  }

  // Handle the event
  switch (event.event_type) {
    case 'media.completed':
      handleMediaCompleted(event.data);
      break;
    default:
      console.log(`Unhandled event type: ${event.event_type}`);
  }

  res.status(200).send('OK');
});

function handleMediaCompleted(data) {
  console.log('Media processing completed:', data.job_id);
  console.log('Status:', data.status);
  
  if (data.status === 'completed') {
    // Process the results
    console.log('Results:', data.results);
    // Update your database, notify users, etc.
    updateDatabase(data.job_id, data.results);
    notifyUser(data.job_id, 'completed');
  } else if (data.status === 'failed') {
    // Handle failure
    console.error('Media processing failed for job:', data.job_id);
    notifyUser(data.job_id, 'failed');
  }
}

async function updateDatabase(jobId, results) {
  // Update your database with the processing results
  // Example: await db.jobs.update(jobId, { status: 'completed', results });
}

async function notifyUser(jobId, status) {
  // Notify the user about the job completion
  // Example: await sendEmail(jobId, status);
}

app.listen(3000, () => {
  console.log('Webhook server listening on port 3000');
});

Advanced Webhook Handling

Retry Logic and Idempotency

Implement proper retry handling and idempotency:
// Idempotency tracking
const processedEvents = new Set();

function handleWebhookWithIdempotency(event) {
  const eventId = event.data.job_id + '_' + event.timestamp;
  
  // Check if we've already processed this event
  if (processedEvents.has(eventId)) {
    console.log('Event already processed:', eventId);
    return;
  }
  
  try {
    // Process the event
    handleMediaCompleted(event.data);
    
    // Mark as processed
    processedEvents.add(eventId);
    
    // Clean up old entries (keep last 1000)
    if (processedEvents.size > 1000) {
      const oldestEntries = Array.from(processedEvents).slice(0, 100);
      oldestEntries.forEach(entry => processedEvents.delete(entry));
    }
    
  } catch (error) {
    console.error('Error processing webhook:', error);
    // Don't mark as processed so it can be retried
    throw error;
  }
}

Webhook Queue Processing

For high-volume applications, consider using a queue:
// Using Bull Queue for Redis-backed job processing
const Queue = require('bull');
const webhookQueue = new Queue('webhook processing');

// Add webhook to queue instead of processing immediately
app.post('/webhook', (req, res) => {
  // ... signature verification ...
  
  // Add to queue for async processing
  webhookQueue.add('process-webhook', event, {
    attempts: 3,
    backoff: {
      type: 'exponential',
      delay: 2000,
    },
  });
  
  res.status(200).send('OK');
});

// Process webhooks from queue
webhookQueue.process('process-webhook', async (job) => {
  const event = job.data;
  
  switch (event.event_type) {
    case 'media.completed':
      await handleMediaCompleted(event.data);
      break;
    default:
      console.log(`Unhandled event type: ${event.event_type}`);
  }
});

Best Practices

  1. Always verify signatures - This ensures the webhook is actually from Animus and not a malicious actor
  2. Respond quickly - Return a 200 status code as soon as possible. Do heavy processing asynchronously
  3. Handle retries - If your endpoint is down, we’ll retry the webhook. Make sure your handler is idempotent
  4. Log events - Keep logs of received webhooks for debugging and monitoring
  5. Use HTTPS - Always use HTTPS endpoints for security
  6. Handle failures gracefully - Your webhook handler should be robust and handle unexpected data
  7. Implement rate limiting - Protect your endpoint from potential abuse
  8. Monitor webhook health - Set up alerts for failed webhook deliveries

Testing Webhooks

You can test your webhook integration by sending sample webhook events to your endpoints. This allows you to verify that your application correctly processes the webhook events before deploying to production. Testing webhook deliveries During development, you can also use tools like:
  • ngrok - To expose your local development server to the internet
  • Svix Play - A webhook testing tool that provides a temporary URL for testing
  • Webhook.site - Another testing service for inspecting webhook payloads

Local Development Setup

# Install ngrok
npm install -g ngrok

# Start your local server
node webhook-server.js

# In another terminal, expose your local server
ngrok http 3000

# Use the ngrok URL in your webhook configuration
# Example: https://abc123.ngrok.io/webhook

Troubleshooting

Common issues and solutions:
  • Webhook not received: Check that your endpoint is publicly accessible and returns a 200 status code
  • Signature verification fails: Ensure you’re using the correct webhook secret and the raw request body
  • Timeouts: Make sure your webhook handler responds within 30 seconds
  • Duplicate events: Implement idempotency checks using the event ID or timestamp
  • Missing events: Check your webhook endpoint configuration and ensure it’s subscribed to the correct event types

Monitoring and Observability

Implement proper monitoring for your webhook endpoints:
// Add metrics and monitoring
const prometheus = require('prom-client');

const webhookCounter = new prometheus.Counter({
  name: 'webhooks_received_total',
  help: 'Total number of webhooks received',
  labelNames: ['event_type', 'status']
});

const webhookDuration = new prometheus.Histogram({
  name: 'webhook_processing_duration_seconds',
  help: 'Time spent processing webhooks',
  labelNames: ['event_type']
});

app.post('/webhook', async (req, res) => {
  const startTime = Date.now();
  
  try {
    // ... webhook processing ...
    
    webhookCounter.inc({ event_type: event.event_type, status: 'success' });
  } catch (error) {
    webhookCounter.inc({ event_type: event.event_type, status: 'error' });
    throw error;
  } finally {
    const duration = (Date.now() - startTime) / 1000;
    webhookDuration.observe({ event_type: event.event_type }, duration);
  }
});

Next Steps