Webhooks
Receive real-time notifications when events occur in your connected integrations. Unizo standardizes webhook events across all platforms, giving you a consistent interface for GitHub, GitLab, Jira, ServiceNow, and more.
How Unizo Webhooks Work
Unlike traditional webhooks where you need to register endpoints with each platform individually, Unizo provides:
- Single Endpoint Registration: Register once with Unizo, receive events from all integrations
- Normalized Events: Consistent event schema regardless of the source platform
- Unified Authentication: Use your Unizo API key instead of managing multiple webhook secrets
- Cross-Platform Events: Correlate events across different tools (e.g., link GitHub commits to Jira tickets)
Setting Up Webhooks
Step 1: Create a Webhook Endpoint in Unizo Console
- Navigate to Unizo Console
- Click "Create Webhook Endpoint"
- Configure your endpoint:
- URL: Your application's webhook receiver URL
- Events: Select specific events or use
*
for all events - Integrations: Choose which integrations to receive events from
Step 2: Configure Your Webhook Receiver
Create an endpoint in your application to receive webhook events:
// Express.js example
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
// Webhook endpoint
app.post('/webhooks/unizo', async (req, res) => {
const signature = req.headers['x-unizo-signature'];
const timestamp = req.headers['x-unizo-timestamp'];
// Verify webhook authenticity
if (!verifyWebhook(req.body, signature, timestamp)) {
return res.status(401).send('Unauthorized');
}
// Process the event
const event = req.body;
console.log(`Received ${event.type} event from ${event.integration.name}`);
// Acknowledge receipt
res.status(200).send('OK');
// Process asynchronously
processWebhookEvent(event);
});
Step 3: Verify Webhook Signatures
Unizo signs all webhook payloads using HMAC-SHA256:
function verifyWebhook(payload, signature, timestamp) {
const webhookSecret = process.env.UNIZO_WEBHOOK_SECRET;
// Prevent replay attacks
const currentTime = Math.floor(Date.now() / 1000);
if (Math.abs(currentTime - parseInt(timestamp)) > 300) { // 5 minutes
return false;
}
// Verify signature
const expectedSignature = crypto
.createHmac('sha256', webhookSecret)
.update(`${timestamp}.${JSON.stringify(payload)}`)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
Step 4: Handle Different Event Types
Process events based on their type and source integration:
async function processWebhookEvent(event) {
const { type, integration, data } = event;
// Handle events by category
if (type.startsWith('scm.')) {
await handleSourceCodeEvent(type, integration, data);
} else if (type.startsWith('ticketing.')) {
await handleTicketingEvent(type, integration, data);
} else if (type.startsWith('identity.')) {
await handleIdentityEvent(type, integration, data);
}
}
// Example: Handle pull request events from any SCM platform
async function handleSourceCodeEvent(type, integration, data) {
if (type === 'scm.pull_request.created') {
// Same code works for GitHub, GitLab, Bitbucket, etc.
console.log(`New PR in ${integration.name}: ${data.title}`);
// Automatically create a linked ticket
await unizo.ticketing.createTicket({
integrationId: 'jira-prod',
title: `Review PR: ${data.title}`,
description: `PR #${data.number} needs review\n${data.url}`,
customFields: {
pr_url: data.url,
repository: data.repository.name
}
});
}
}
Event Structure
All webhook events follow a consistent structure:
{
"id": "evt_1234567890",
"type": "scm.pull_request.created",
"timestamp": "2024-01-15T10:30:00Z",
"integration": {
"id": "int_github_123",
"type": "github"
},
"data": {
// Event-specific data
}
}
Available Events by Category
Source Code Management
scm.repository.created
scm.pull_request.created
scm.pull_request.updated
scm.pull_request.merged
scm.commit.pushed
scm.branch.created
scm.branch.deleted
Ticketing
ticketing.ticket.created
ticketing.ticket.updated
ticketing.ticket.resolved
ticketing.comment.added
Identity
identity.user.created
identity.user.updated
identity.user.deactivated
identity.group.created
identity.group.membership.changed
Incident Management
incidents.incident.created
incidents.incident.acknowledged
incidents.incident.resolved
incidents.severity.changed
Best Practices
Idempotency
Store event IDs to handle duplicate deliveries:
const processedEvents = new Set();
function handleWebhook(event) {
if (processedEvents.has(event.id)) {
return; // Already processed
}
// Process event
processedEvents.add(event.id);
}
Async Processing
Return 200 OK immediately and process events asynchronously:
app.post('/webhooks', async (req, res) => {
// Acknowledge receipt immediately
res.status(200).send('OK');
// Process asynchronously
await queue.push({
type: 'webhook',
payload: req.body
});
});
Error Handling
Implement proper error handling and logging:
try {
await processWebhookEvent(event);
} catch (error) {
logger.error('Webhook processing failed', {
eventId: event.id,
error: error.message
});
// Return 500 to trigger retry
res.status(500).send('Processing failed');
}
Webhook Security
IP Whitelisting
Restrict webhook delivery to Unizo's IP addresses:
52.10.128.0/24
54.187.92.0/24
Request Validation
Always validate:
- Signature header matches payload
- Timestamp is within acceptable range (±5 minutes)
- Event ID hasn't been processed before
Retry Policy
Failed webhook deliveries are retried with exponential backoff:
- 1st retry: 1 minute
- 2nd retry: 5 minutes
- 3rd retry: 30 minutes
- 4th retry: 2 hours
- 5th retry: 12 hours
After 5 failed attempts, the webhook is marked as failed and can be manually replayed from the console.
Testing Webhooks
Use the Unizo Console to:
- Send test events to your endpoint
- View delivery attempts and responses
- Replay failed webhooks
- Monitor webhook metrics