Skip to main content

Identity Webhooks

Webhooks enable your applications to receive real-time notifications when events occur in your identity and access management systems. This eliminates the need for polling and ensures your systems stay synchronized with user provisioning, authentication, and access control changes across all integrated platforms.

Unizo normalizes webhook events from Okta, Auth0, Azure AD, OneLogin, and other identity providers into a consistent format. This means you write your webhook handler once and it works with all supported platforms.

Webhook Configuration

To set up webhooks for your integration, visit the Unizo Console Webhooks section for step-by-step configuration guide.

Supported Event Types

These are the event types currently supported by Unizo's Identity webhooks. The list keeps growing as we add support for more events across different platforms.

Event TypeDescriptionTrigger Conditions
user:createdA new user has been createdUser account creation via UI, API, or sync
user:updatedUser profile information has been modifiedProfile updates, attribute changes, or status updates
user:deletedA user account has been deletedUser deletion or deactivation

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

HeaderDescription
x-unizo-event-typeThe type of event that triggered the webhook
x-unizo-signatureHMAC-SHA256 signature of the payload, prefixed with v1= (e.g., v1=ee084789...)
x-unizo-timestampUnix epoch timestamp (seconds) when the request was signed
x-unizo-delivery-idUnique 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

  1. Parse the timestamp from the x-unizo-timestamp header and reject the request if it falls outside your tolerance window (recommended: 5 minutes).
  2. Reconstruct the signed payload by concatenating the timestamp, a literal dot (.), and the raw request body.
  3. Compute the expected signature using HMAC-SHA256 with your webhook signing secret as the key.
  4. Strip the v1= prefix from the x-unizo-signature header to get the received signature.
  5. 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

User Created

user:created

Triggered when a new user account is created in the identity system
POSThttps://api.yourapp.com/webhooks/unizo/identity
Headers
NameTypeRequiredDescription
Content-TypestringYesAlways application/json
x-unizo-event-typestringYesEvent type: user:created
x-unizo-webhook-idstringYesUnique webhook configuration ID
x-unizo-delivery-idstringYesUnique delivery ID for idempotency
x-unizo-signaturestringYesHMAC-SHA256 signature, prefixed with v1=
Request Body Schema
PropertyTypeRequiredDescription
typestringYesEvent type identifier
versionstringYesWebhook payload version
user.idstringYesUnique user identifier
user.emailstringYesUser's email address
user.usernamestringNoUser's username
user.firstNamestringYesUser's first name
user.lastNamestringYesUser's last name
user.statusstringYesUser status: active, pending, suspended
user.createdDateTimestringYesISO 8601 timestamp
user.createdByobjectNoUser who created this account
integrationobjectYesIntegration details
Example Payload
{
"type": "user:created",
"version": "1.0.0",
"user": {
"id": "user-123456",
"email": "[email protected]",
"username": "john.doe",
"firstName": "John",
"lastName": "Doe",
"status": "active",
"createdDateTime": "2024-01-15T14:00:00Z",
"createdBy": {
"id": "admin-789",
"email": "[email protected]"
}
},
"integration": {
"type": "IDENTITY",
"id": "int_123456",
"name": "Okta Production",
"provider": "okta"
}
}
Response
200 OKWebhook processed successfully
400 Bad RequestInvalid webhook payload
401 UnauthorizedInvalid or missing signature

User Updated

user:updated

Triggered when user profile information is modified
POSThttps://api.yourapp.com/webhooks/unizo/identity
Headers
NameTypeRequiredDescription
Content-TypestringYesAlways application/json
x-unizo-event-typestringYesEvent type: user:updated
x-unizo-webhook-idstringYesUnique webhook configuration ID
x-unizo-delivery-idstringYesUnique delivery ID for idempotency
x-unizo-signaturestringYesHMAC-SHA256 signature, prefixed with v1=
Request Body Schema
PropertyTypeRequiredDescription
typestringYesEvent type identifier
versionstringYesWebhook payload version
user.idstringYesUnique user identifier
user.emailstringYesUser's email address
user.changesobjectYesObject containing changed fields
user.updatedDateTimestringYesISO 8601 timestamp
user.updatedByobjectNoUser who made the update
integrationobjectYesIntegration details
Example Payload
{
"type": "user:updated",
"version": "1.0.0",
"user": {
"id": "user-123456",
"email": "[email protected]",
"changes": {
"lastName": {
"from": "Doe",
"to": "Smith"
},
"department": {
"from": "Engineering",
"to": "Product"
}
},
"updatedDateTime": "2024-01-15T15:00:00Z",
"updatedBy": {
"id": "admin-789",
"email": "[email protected]"
}
},
"integration": {
"type": "IDENTITY",
"id": "int_123456",
"name": "Okta Production",
"provider": "okta"
}
}
Response
200 OKWebhook processed successfully
400 Bad RequestInvalid webhook payload
401 UnauthorizedInvalid or missing signature

User Deleted

user:deleted

Triggered when a user account is deleted or deactivated
POSThttps://api.yourapp.com/webhooks/unizo/identity
Headers
NameTypeRequiredDescription
Content-TypestringYesAlways application/json
x-unizo-event-typestringYesEvent type: user:deleted
x-unizo-webhook-idstringYesUnique webhook configuration ID
x-unizo-delivery-idstringYesUnique delivery ID for idempotency
x-unizo-signaturestringYesHMAC-SHA256 signature, prefixed with v1=
Request Body Schema
PropertyTypeRequiredDescription
typestringYesEvent type identifier
versionstringYesWebhook payload version
user.idstringYesUnique user identifier
user.emailstringYesUser's email address
user.deletedDateTimestringYesISO 8601 timestamp
user.deletedByobjectNoUser who deleted this account
integrationobjectYesIntegration details
Example Payload
{
"type": "user:deleted",
"version": "1.0.0",
"user": {
"id": "user-123456",
"email": "[email protected]",
"deletedDateTime": "2024-01-15T16:00:00Z",
"deletedBy": {
"id": "admin-789",
"email": "[email protected]"
}
},
"integration": {
"type": "IDENTITY",
"id": "int_123456",
"name": "Okta Production",
"provider": "okta"
}
}
Response
200 OKWebhook processed successfully
400 Bad RequestInvalid webhook payload
401 UnauthorizedInvalid or missing signature

Webhook Delivery & Retries

Unizo implements automatic retry logic for failed webhook deliveries:

  1. Initial Delivery: Immediate
  2. First Retry: After 1 minute
  3. Second Retry: After 5 minutes
  4. Third Retry: After 15 minutes
  5. Final Retry: After 1 hour

Webhooks are considered failed if:

  • Your endpoint returns a non-2xx status code
  • Connection timeout (30 seconds)
  • SSL/TLS errors

Best Practices

1. Idempotency

Idempotent Webhook Handler

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

Asynchronous Processing

app.post('/webhooks/identity', (req, res) => {
// Validate signature
if (!verifySignature(req)) {
  return res.status(401).send('Invalid signature');
}

// Queue for processing
identityQueue.add(req.body);

// Return immediately
res.status(200).send('OK');
});

3. Error Handling

Comprehensive Error Handling

async function processWebhook(payload) {
try {
  switch (payload.type) {
    case 'user:created':
      await handleUserCreated(payload);
      break;
    case 'role:assigned':
      await handleRoleAssigned(payload);
      break;
    default:
      logger.warn(`Unknown webhook type: ${payload.type}`);
  }
} catch (error) {
  logger.error('Webhook processing failed', {
    error: error.message,
    payload,
    stack: error.stack
  });
  throw error;
}
}

Need Help?

For webhook-related support: