Webhook Examples
This page provides complete, production-ready examples for receiving and processing Ignite webhooks in different programming languages.
Complete Server Examples
const express = require('express');
const crypto = require('crypto');
const app = express();
const WEBHOOK_SECRET = process.env.WEBHOOK_SIGNING_SECRET;
// Parse JSON and preserve raw body for signature verification
app.use(express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString();
}
}));
function verifyWebhookSignature(rawBody, signatureHeader, secret) {
const parts = signatureHeader.split(',');
const timestampPart = parts.find(p => p.startsWith('t='));
const signaturePart = parts.find(p => p.startsWith('v1='));
if (!timestampPart || !signaturePart) {
return false;
}
const timestamp = timestampPart.split('=')[1];
const receivedSignature = signaturePart.split('=')[1];
// Reconstruct signed payload using raw body
const signedPayload = `${timestamp}.${rawBody}`;
const hmac = crypto.createHmac('sha256', secret);
const calculatedSignature = hmac.update(signedPayload).digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(receivedSignature),
Buffer.from(calculatedSignature)
);
} catch {
return false;
}
}
// Store processed delivery IDs (use a database in production)
const processedDeliveries = new Set();
app.post('/webhook', async (req, res) => {
const signature = req.headers['x-webhook-signature'];
const eventType = req.headers['x-webhook-event'];
const deliveryId = req.headers['x-webhook-delivery-id'];
// 1. Verify signature
if (!verifyWebhookSignature(req.rawBody, signature, WEBHOOK_SECRET)) {
console.error('Invalid webhook signature');
return res.status(401).json({ error: 'Invalid signature' });
}
// 2. Check for duplicate deliveries (idempotency)
if (processedDeliveries.has(deliveryId)) {
console.log('Duplicate delivery, skipping:', deliveryId);
return res.status(200).json({ received: true, duplicate: true });
}
// 3. Get video data from body
const video = req.body;
// 4. Process based on event type
try {
switch(eventType) {
case 'video.created':
console.log('New video created:', video.id, video.title);
await handleVideoCreated(video);
break;
case 'video.updated':
console.log('Video updated:', video.id, video.title);
await handleVideoUpdated(video);
break;
case 'video.deleted':
console.log('Video deleted:', video.id);
await handleVideoDeleted(video);
break;
default:
console.log('Unknown event type:', eventType);
}
// Mark as processed
processedDeliveries.add(deliveryId);
res.status(200).json({ received: true });
} catch (error) {
console.error('Error processing webhook:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Event handlers
async function handleVideoCreated(video) {
// Example: Sync to your CMS
// await cms.createVideo({
// externalId: video.id,
// title: video.title,
// status: video.status
// });
}
async function handleVideoUpdated(video) {
// Example: Update your CMS when video processing completes
// if (video.status === 'COMPLETE') {
// await cms.updateVideo(video.id, {
// streamUrl: video.src.abr.url,
// thumbnailUrl: video.src.thumbnailUrl,
// duration: video.duration
// });
// }
}
async function handleVideoDeleted(video) {
// Example: Remove from your CMS
// await cms.deleteVideo(video.id);
}
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Webhook endpoint listening on port ${PORT}`);
});Webhook Deliveries
Every webhook delivery attempt is logged. If a delivery fails, the system will automatically retry.
Delivery Status
| Status | Description |
|---|---|
success | Event was successfully delivered (HTTP 2xx response) |
failed | Delivery failed (timeout, HTTP 4xx/5xx, network error) |
Delivery Information
Each delivery record contains:
| Field | Description |
|---|---|
responseCode | HTTP status code returned by your endpoint |
responseBody | Response body from your endpoint (truncated for large responses) |
errorMessage | Error message if delivery failed |
deliveredAt | Timestamp of the delivery attempt |
attemptNumber | Which attempt this was (increments with retries) |
nextRetryAt | When the next retry will be attempted (if applicable) |
Retry Behavior
- Failed deliveries are automatically retried
- If
nextRetryAtis set, a retry is scheduled - If
nextRetryAtis not set, no more retries will be attempted
Best Practices
Following these best practices ensures reliable webhook processing and helps with debugging issues.
Response Time
Your endpoint must respond within 30 seconds. For long-running operations, acknowledge the webhook immediately and process asynchronously:
app.post('/webhook', async (req, res) => {
// Immediately acknowledge receipt
res.status(200).json({ received: true });
// Process asynchronously (don't await)
processWebhookAsync(req.body).catch(console.error);
});
async function processWebhookAsync(video) {
// Long-running operations here
}Security
- Keep secrets secure — Never expose your signing secret in client-side code or version control
- Use HTTPS — Always use HTTPS endpoints in production
- Validate everything — Always verify signatures before processing
- Rate limiting — Consider implementing rate limiting on your endpoint
Reliability
- Idempotency — Store and check the
X-Webhook-Delivery-Idto handle duplicate deliveries - Logging — Log all webhook requests for debugging
- Monitoring — Set up alerts for failed webhook processing
- Error handling — Return appropriate HTTP status codes so retries work correctly
HTTP Status Codes
| Code | Meaning |
|---|---|
200-299 | Success — event processed |
4xx | Client error — will be retried |
5xx | Server error — will be retried |
Troubleshooting
No Events Received
- Check if the webhook subscription is set to Active
- Verify the correct event types are selected
- Ensure events are happening in the correct workspace
- Check your server logs for incoming requests
Signature Verification Fails
- Verify you're using the correct signing secret (not an API token)
- Ensure the request body is not modified before verification
- Check JSON serialization matches (compact, no extra whitespace)
- Verify the signature header format is parsed correctly:
t=timestamp,v1=signature
Timeout Errors
- Respond within 30 seconds
- Move long operations to background processing
- Return
200immediately, process asynchronously
Duplicate Events
- Implement idempotency using
X-Webhook-Delivery-Id - Check
attemptNumberfor retries - Store processed delivery IDs in a database