Apache Syncope is an open-source Identity Governance and Administration (IGA) platform that manages user identities, roles, and entitlements across connected systems via the ConnID framework. Syncope orchestrates provisioning workflows across LDAP directories, HR systems, and SaaS applications — when a ConnID connector pool is exhausted, the provisioning engine silently queues tasks that never execute, users get provisioned hours or days late while their manager believes the account was created immediately; when a Quartz-based propagation task throws an exception mid-execution, Syncope marks the task as failed in its internal database but sends no external signal — operations teams only discover the failure when an audit finds unprovision tasks that never ran; when Syncope's embedded Activiti workflow engine encounters a corrupted process definition after an upgrade, approval workflows queue indefinitely and no approvals or rejections are ever processed. These are identity security incidents disguised as system behavior.
Vigilmon gives you external visibility into Syncope's identity governance layer through HTTP probe monitoring and heartbeat monitors. This tutorial covers both.
Why Syncope Needs External Monitoring
Syncope's failure modes are subtle and high-impact:
- ConnID connector pool exhaustion: each ConnID connector has a configured pool of connections to the target system; under high provisioning load, the pool fills and subsequent tasks block indefinitely — the Syncope UI shows tasks queued but not executing, with no external alert
- Propagation task failure accumulation: Syncope's propagation engine retries failed tasks on a configurable schedule; if the retry schedule is misconfigured after an upgrade, failed tasks accumulate in the
FAILUREstate indefinitely while the system appears healthy from the outside - Workflow stall on Activiti engine: Syncope uses Activiti for approval workflows; a corrupt or incompatible process definition after an upgrade causes all new workflow instances to stall in the
ACTIVEstate — approvers see no pending approvals and no notifications are sent - REST API authentication failure: Syncope's REST API uses bearer token authentication; a misconfigured
keymaster.conf.locationafter a redeployment can cause all API calls to return 401, disabling all external integrations while the Syncope UI (which uses a different auth path) continues to work - Reconciliation task stall: Syncope's reconciliation tasks compare the identity store with connected systems; a timeout on a slow LDAP connector causes reconciliation to stall mid-execution, holding a task lock that blocks subsequent scheduled reconciliation runs indefinitely
- Attribute mapping failure: a schema migration that removes an attribute that is still referenced in a connector's attribute mapping causes propagation to throw
MappingManagerExceptionfor every provisioning event — accounts are never provisioned but Syncope shows no error on the user record itself
External monitoring with Vigilmon adds:
- Proactive alerting when Syncope's actuator or REST API endpoints report failures
- Provisioning engine availability monitoring independent of the Syncope UI state
- Heartbeat monitoring so you know when scheduled provisioning, reconciliation, or audit export jobs stop completing
- Multi-region probe consensus that filters transient authentication token blips from genuine Syncope provisioning failures
Step 1: Build a Syncope Health Endpoint
Syncope 3.x ships with Spring Boot Actuator. For older versions, add a dedicated REST health endpoint.
Spring Boot Actuator (Syncope 3.x)
Syncope 3.x exposes /actuator/health by default. Enable detailed health output in core/src/main/resources/application.properties:
management.endpoint.health.show-details=always
management.endpoints.web.exposure.include=health,info,metrics
The /actuator/health response includes:
{
"status": "UP",
"components": {
"db": { "status": "UP", "details": { "database": "H2", "validationQuery": "isValid()" } },
"diskSpace": { "status": "UP" },
"ping": { "status": "UP" }
}
}
Custom Syncope Health Resource (Syncope 2.x / JAX-RS)
// rest/SyncopeHealthEndpoint.java
package com.yourorg.syncope.rest;
import org.apache.syncope.core.persistence.api.dao.TaskDAO;
import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
import org.apache.syncope.common.lib.types.TaskType;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import java.util.*;
@Path("/health/syncope")
@Produces(MediaType.APPLICATION_JSON)
public class SyncopeHealthEndpoint {
@Inject
private TaskDAO taskDAO;
@GET
public Response health() {
Map<String, Object> result = new LinkedHashMap<>();
Map<String, String> checks = new LinkedHashMap<>();
boolean anyFailed = false;
// Check for accumulated propagation failures
try {
long failedTasks = taskDAO.count(TaskType.PROPAGATION)
.stream()
.filter(t -> "FAILURE".equals(t.getLatestExecStatus()))
.count();
if (failedTasks > 10) {
checks.put("propagation_failures", "high:" + failedTasks);
anyFailed = true;
} else {
checks.put("propagation_failures", "ok:" + failedTasks);
}
} catch (Exception e) {
checks.put("propagation_failures", "down");
anyFailed = true;
}
result.put("status", anyFailed ? "degraded" : "ok");
result.put("checks", checks);
return Response
.status(anyFailed ? Response.Status.SERVICE_UNAVAILABLE : Response.Status.OK)
.entity(result)
.build();
}
}
Node.js Sidecar
// health/syncope.js
const express = require('express');
const http = require('http');
const app = express();
const APP_HOST = process.env.APP_HOST || 'localhost';
const APP_PORT = parseInt(process.env.APP_PORT || '8080');
const HEALTH_PATH = process.env.SYNCOPE_HEALTH_PATH || '/syncope/rest/actuator/health';
const SYNCOPE_USER = process.env.SYNCOPE_ADMIN_USER || 'admin';
const SYNCOPE_PASS = process.env.SYNCOPE_ADMIN_PASS || '';
function probeSyncope() {
return new Promise((resolve, reject) => {
const auth = Buffer.from(`${SYNCOPE_USER}:${SYNCOPE_PASS}`).toString('base64');
const req = http.get(
{
host: APP_HOST,
port: APP_PORT,
path: HEALTH_PATH,
timeout: 5000,
headers: { Authorization: `Basic ${auth}` },
},
res => {
let body = '';
res.on('data', chunk => { body += chunk; });
res.on('end', () => resolve({ status: res.statusCode, body }));
}
);
req.on('error', reject);
req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
});
}
app.get('/health/syncope', async (req, res) => {
try {
const result = await probeSyncope();
const up = result.status === 200 &&
(result.body.includes('"status":"UP"') || result.body.includes('"status":"ok"'));
if (!up) {
return res.status(503).json({
status: 'down',
upstream_status: result.status,
body: result.body.substring(0, 500),
});
}
return res.status(200).json({ status: 'ok' });
} catch (err) {
return res.status(503).json({ status: 'down', error: err.message });
}
});
app.listen(3018);
Python Health Sidecar
# health_syncope.py
import os, requests
from flask import Flask, jsonify
app = Flask(__name__)
APP_BASE = f"http://{os.environ.get('APP_HOST', 'localhost')}:{os.environ.get('APP_PORT', '8080')}"
HEALTH_PATH = os.environ.get('SYNCOPE_HEALTH_PATH', '/syncope/rest/actuator/health')
AUTH = (
os.environ.get('SYNCOPE_ADMIN_USER', 'admin'),
os.environ.get('SYNCOPE_ADMIN_PASS', '')
)
@app.route('/health/syncope')
def health():
try:
resp = requests.get(f'{APP_BASE}{HEALTH_PATH}', auth=AUTH, timeout=5)
if resp.status_code != 200:
return jsonify({'status': 'down', 'upstream_status': resp.status_code}), 503
data = resp.json()
status = data.get('status', '')
if status not in ('UP', 'ok'):
return jsonify({'status': 'degraded', 'detail': data}), 503
return jsonify({'status': 'ok', 'components': data.get('components', {})})
except Exception as e:
return jsonify({'status': 'down', 'error': str(e)}), 503
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3018)
Step 2: Configure Vigilmon HTTP Monitor for Syncope
- Log in to vigilmon.online and go to Monitors → New Monitor
- Choose HTTP / HTTPS
- Set the URL to your Syncope health endpoint:
https://syncope.your-org.example.com/syncope/rest/actuator/health - Set the check interval to 1 minute
- Under Expected response, configure:
- Status code:
200 - Response body contains:
"status":"UP" - Response time threshold:
5000ms
- Status code:
- Under Alert channels, assign your Slack or PagerDuty channel
- Save the monitor
Add a second monitor for the Syncope REST API availability:
- URL:
https://syncope.your-org.example.com/syncope/rest/users?page=1&size=1 - Expected:
200 - Interval: 2 minutes
- Alert channel: P1 pager — REST API failure means all external provisioning integrations and SCIM consumers are blocked
For Syncope UI availability, add a third monitor:
- URL:
https://syncope.your-org.example.com/syncope-console/ - Expected:
200 - Interval: 5 minutes
- Alert channel: Slack — UI failure blocks self-service and admin operations
Vigilmon's multi-region probe consensus filters transient JWT token refresh events from genuine Syncope REST API failures.
Step 3: Heartbeat Monitoring for Provisioning and Reconciliation Jobs
Health endpoint monitoring catches database and API availability failures, but not end-to-end provisioning and reconciliation problems. Syncope can report healthy via Actuator while scheduled reconciliation tasks have been silently stalling due to a ConnID connector timeout holding a task lock.
Vigilmon heartbeat monitors catch these: your Syncope provisioning and reconciliation jobs ping Vigilmon after each successful execution.
Set Up Heartbeat Monitors
For provisioning job liveness:
- In Vigilmon, go to Monitors → New Monitor → Heartbeat
- Set the name:
syncope-propagation-jobs - Set the expected interval: 30 minutes
- Set the grace period: 60 minutes
- Save — copy the heartbeat URL
For reconciliation liveness:
- Create another Heartbeat monitor named
syncope-reconciliation - Set the expected interval: 4 hours
- Set the grace period: 5 hours
- Save — copy the heartbeat URL
Wire Heartbeats Via Syncope REST API Job Hooks
Syncope 3.x supports job delegates and task notification hooks. Add a heartbeat ping to a custom SchedTaskCommand:
// task/PropagationMonitorTask.java
package com.yourorg.syncope.task;
import org.apache.syncope.core.provisioning.api.job.SchedTaskJobDelegate;
import org.apache.syncope.core.persistence.api.entity.task.SchedTask;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;
import java.net.http.*;
import java.net.URI;
@Component
public class PropagationMonitorTask implements SchedTaskJobDelegate {
private final HttpClient http = HttpClient.newHttpClient();
private final String heartbeatUrl = System.getenv("VIGILMON_PROPAGATION_HEARTBEAT_URL");
@Override
public String execute(SchedTask task, boolean dryRun, JobExecutionContext context)
throws JobExecutionException {
try {
// Check for stuck propagation tasks and resolve them
checkAndResolvePropagationFailures();
pingHeartbeat(heartbeatUrl);
return "Propagation health check completed";
} catch (Exception e) {
throw new JobExecutionException("Propagation health check failed", e);
}
}
private void checkAndResolvePropagationFailures() throws Exception {
// Query Syncope REST API for failed tasks and alert
}
private void pingHeartbeat(String url) {
if (url == null) return;
try {
http.sendAsync(
HttpRequest.newBuilder(URI.create(url)).GET().build(),
HttpResponse.BodyHandlers.discarding()
);
} catch (Exception ignored) {}
}
}
For standard reconciliation tasks configured via the Syncope admin console, add the heartbeat URL as an environment variable and invoke it from a post-execution hook script:
#!/bin/bash
# post-reconciliation-hook.sh — runs after each reconciliation task completes
if [ -n "$VIGILMON_RECONCILIATION_HEARTBEAT_URL" ]; then
curl -s "$VIGILMON_RECONCILIATION_HEARTBEAT_URL" > /dev/null 2>&1 || true
fi
Step 4: Alert Routing for Syncope Failures
Syncope failures range from a single connector pool being exhausted to the entire provisioning engine stalling. Route alerts by severity:
| Monitor | Alert Channel | Priority |
|---|---|---|
| HTTP: /actuator/health Syncope status | Slack + PagerDuty | P1 |
| HTTP: REST API users endpoint | Slack + PagerDuty | P1 |
| HTTP: Syncope console UI | Slack | P2 |
| Heartbeat: propagation jobs | Slack + PagerDuty | P1 |
| Heartbeat: reconciliation tasks | Slack + PagerDuty | P1 |
| Heartbeat: audit export | Slack + email | P2 |
Set response time thresholds as early warning signals:
- Alert at
2000msfor the Actuator health check (slow responses indicate database or connector pool pressure) - Alert at
3000msfor the REST API check (REST API slowdowns indicate provisioning engine backpressure) - Alert at
60 minutesgap in propagation job heartbeat (stuck propagation means accounts are not being created or updated) - Alert at
5 hoursgap in reconciliation heartbeat during business hours
For production IGA deployments, set up a status page in Vigilmon showing provisioning engine availability and reconciliation job liveness — this lets identity operations engineers identify Syncope failures before they manifest as access requests that silently never complete or roles that are never revoked after an employee departure.
Summary
Syncope failures present as silent provisioning delays or access governance gaps before they escalate to full IGA platform outages — HR and IT teams see accounts that were never created or roles that were never revoked while Syncope's health actuator reports UP because the HTTP layer is healthy. External monitoring catches these before they become a security incident:
| Monitor Type | What It Covers |
|---|---|
| HTTP monitor on /actuator/health | Database, disk, and Syncope core availability |
| HTTP monitor on REST API endpoint | Provisioning REST layer authentication and response |
| HTTP monitor on console UI | Admin and self-service interface availability |
| Heartbeat monitor (propagation jobs) | End-to-end provisioning task execution success |
| Heartbeat monitor (reconciliation tasks) | Identity store reconciliation and sync job liveness |
Get started free at vigilmon.online — your first Syncope identity governance monitor is running in under two minutes.