Webhook Documentation Template
To set up webhooks for your integration, visit the Unizo Console Webhooks section for step-by-step configuration guide.
| Event Type | Description | Trigger Conditions |
|---|---|---|
| resource:created | A new resource has been created | Resource creation via API or UI |
| resource:updated | Resource has been modified | Any resource property change |
| resource:deleted | Resource has been deleted | Resource deletion |
Webhook Security
Every webhook request sent by Unizo includes a cryptographic signature so you can verify that the payload is authentic and has not been tampered with.
Security Headers
| Header | Description |
|---|---|
x-unizo-event-type | The type of event that triggered the webhook |
x-unizo-signature | HMAC-SHA256 signature of the payload, prefixed with v1= (e.g., v1=ee084789...) |
x-unizo-timestamp | Unix epoch timestamp (seconds) when the request was signed |
x-unizo-delivery-id | Unique identifier for this webhook delivery |
Signature Verification
The signed payload is constructed by joining the timestamp and the raw request body with a dot separator: {timestamp}.{payload}. This ensures the timestamp is covered by the signature, preventing replay attacks.
Verification Steps
- Parse the timestamp from the
x-unizo-timestampheader and reject the request if it falls outside your tolerance window (recommended: 5 minutes). - Reconstruct the signed payload by concatenating the timestamp, a literal dot (
.), and the raw request body. - Compute the expected signature using HMAC-SHA256 with your webhook signing secret as the key.
- Strip the
v1=prefix from thex-unizo-signatureheader to get the received signature. - Compare the two signatures using a constant-time comparison function to prevent timing attacks.
Reference Implementation
const crypto = require("crypto");
function verifyWebhookSignature(
payload,
signatureHeader,
timestampHeader,
secret,
toleranceSeconds = 300
) {
// 1. Reject stale timestamps to prevent replay attacks
const now = Math.floor(Date.now() / 1000);
const timestamp = parseInt(timestampHeader, 10);
if (Number.isNaN(timestamp)) {
return false;
}
if (Math.abs(now - timestamp) > toleranceSeconds) {
return false;
}
// 2. Reconstruct the signed payload: "{timestamp}.{payload}"
const signedPayload = `${timestamp}.${payload}`;
// 3. Compute the expected signature
const expected = crypto
.createHmac("sha256", secret)
.update(signedPayload)
.digest("hex");
// 4. Strip the "v1=" prefix from the received signature
const received = signatureHeader.startsWith("v1=")
? signatureHeader.slice(3)
: signatureHeader;
// 5. Constant-time comparison to prevent timing attacks
try {
return crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(received, "hex")
);
} catch {
return false;
}
}
// Usage
const isValid = verifyWebhookSignature(
rawBody, // raw request body string
headers["x-unizo-signature"], // "v1=ee084789..."
headers["x-unizo-timestamp"], // "1774093147"
process.env.UNIZO_WEBHOOK_SECRET // your signing secret
);
if (!isValid) {
return res.status(401).json({ error: "Invalid signature" });
}
// Signature verified — process the event
handleEvent(JSON.parse(rawBody));
Event Details
Resource Events
Resource Created
resource:created
Headers
| Name | Type | Required | Description |
|---|---|---|---|
Content-Type | string | Yes | Always application/json |
x-unizo-event-type | string | Yes | Event type: resource:created |
x-unizo-signature | string | Yes | HMAC SHA-256 signature of the request body |
x-unizo-timestamp | string | Yes | Unix timestamp when the event was sent |
x-unizo-delivery-id | string | Yes | Unique identifier for this webhook delivery (UUID v4) |
Request Body Schema
| Property | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Event type identifier: resource:created |
version | string | Yes | Webhook payload version (currently 1.0.0) |
contentType | string | Yes | Always application/json |
resource | object | Yes | Resource information |
resource.id | string | Yes | Unique identifier |
resource.name | string | Yes | Resource name |
resource.createdDateTime | string | Yes | ISO 8601 timestamp |
integration | object | Yes | Integration context |
integration.type | string | Yes | Integration type |
integration.id | string | Yes | Integration UUID |
integration.provider | string | Yes | Platform identifier |
Example Payload
{"type": "resource:created","version": "1.0.0","contentType": "application/json","resource": {"id": "res-123456","name": "My Resource","createdDateTime": "2024-01-15T10:30:00Z"},"integration": {"type": "API","id": "int_123456","provider": "example-provider"}}
Response
200 OK | Webhook processed successfully |
400 Bad Request | Invalid webhook payload |
401 Unauthorized | Invalid or missing signature |
resource:deleted
Triggered when a resource is deleted.
Event Payload:
{
"type": "resource:deleted",
"version": "1.0.0",
"contentType": "application/json",
"resource": {
"id": "res-123456",
"deletedDateTime": "2024-01-15T12:00:00Z"
},
"integration": {
"type": "API",
"id": "int_123456",
"provider": "example-provider"
}
}Attribute Descriptions:
resource.id: The ID of the deleted resourceresource.deletedDateTime: When the resource was deleted
Webhook Retry Policy
Unizo implements automatic retry logic for failed webhook deliveries:
- Initial Delivery: Immediate
- First Retry: After 1 minute
- Second Retry: After 5 minutes
- Third Retry: After 15 minutes
- Final Retry: After 1 hour
Best Practices
1. Idempotency
Always design your webhook handlers to be idempotent. Use the x-unizo-delivery-id header to track processed events:
async function handleWebhook(request) {
const deliveryId = request.headers['x-unizo-delivery-id'];
// Check if already processed
if (await isProcessed(deliveryId)) {
return { status: 200, message: 'Already processed' };
}
// Process webhook
await processWebhook(request.body);
// Mark as processed
await markProcessed(deliveryId);
return { status: 200 };
}2. Async Processing
Return a 200 status immediately and process webhooks asynchronously:
app.post('/webhooks/api', (req, res) => {
// Validate signature
if (!verifySignature(req)) {
return res.status(401).send('Invalid signature');
}
// Queue for processing
webhookQueue.add(req.body);
// Return immediately
res.status(200).send('OK');
});Rate Limits
Webhook deliveries are subject to the following limits:
- Per Integration: 10,000 webhooks per hour
- Per Endpoint: 1,000 webhooks per minute
- Payload Size: Maximum 5MB per webhook
Troubleshooting
Common Issues
- Webhooks not received
- Verify endpoint is publicly accessible
- Check webhook configuration in Unizo Console
- Review webhook delivery logs
- Signature validation fails
- Ensure you're using the correct webhook secret
- Verify you're validating the raw request body
- Check for any body parsing middleware issues
- Duplicate events
- Implement idempotency using delivery IDs
- Check if multiple webhook endpoints are configured
- Verify retry logic isn't creating duplicates
Multiple Webhook Registrations: If you have created multiple webhook registrations for the same integration, duplicate events will be sent for each registration. Review all webhook configurations in the Unizo Console.
Webhook Logs
Access detailed webhook logs in the Unizo Console:
- Navigate to your integration
- Click on "Webhooks" tab
- View delivery history with request/response details
Need Help?
For webhook-related support:
- Check our Webhook Troubleshooting Guide
- Contact support at [email protected]
- Join our Developer Community
How to Use This Template
-
Copy this file and rename it for your specific API (e.g.,
crm-webhooks.mdx) -
Update the frontmatter with your API-specific information
-
Customize the components:
- Update the
WebhookHeaderprops with your API name and description - Modify the
WebhookEventTableevents array with your webhook events - Create
WebhookEventcomponents for each detailed event - Add relevant code examples
- Update troubleshooting items specific to your API
- Update the
-
Add or remove sections as needed for your API
-
Test your documentation to ensure all components render correctly
Component Props Reference
WebhookEventTable:
events: Array of event objects withtype,description, andtriggertitle: Optional section title
WebhookEvent:
eventName: Display name for the eventeventType: Technical event type (e.g., "resource:created")description: Event descriptionschema: Array of schema propertiesexamplePayload: JSON example payloadheaders: Optional custom headers arrayresponseCodes: Optional custom response codes
WebhookSecuritySection:
headers: Optional custom security headersshowSignatureVerification: Show verification codeverificationLanguage: Language for code example
WebhookCodeExample:
title: Example titledescription: Optional descriptionlanguage: Programming languagecode: Code string
WebhookTroubleshooting:
items: Array of troubleshooting itemsshowWebhookLogs: Show webhook logs sectionsupportEmail: Support email addresstroubleshootingGuideUrl: Link to guidecommunityUrl: Link to community