ImagID — Eventos
Eventos Producidos
ImagID publica eventos en el exchange imagy.events con routing key subject.*.
subject.profile.flagged
Se publica cuando el sistema detecta un patron sospechoso y marca al sujeto como flagged dentro de un tenant.
| Campo | Valor |
|---|---|
| Routing key | subject.profile.flagged |
| Consumidores | ImagFlow, ImagLend |
| Trigger | Alert Service detecta patron y cambia status a flagged |
{
"event_id": "uuid",
"event_type": "subject.profile.flagged",
"version": "1.0",
"timestamp": "2026-05-18T10:30:00Z",
"correlation_id": "uuid",
"tenant_id": "uuid",
"source": "imagy-subject-360",
"actor_id": "system",
"actor_type": "system",
"data": {
"subject_id": "uuid",
"identifier_type": "cedula",
"identifier_value": "1234567890",
"previous_status": "active",
"new_status": "flagged",
"reason": "multiple_rejections",
"alert_id": "uuid",
"alert_type": "multiple_rejections",
"alert_severity": "high",
"trust_score": 35.00,
"details": {
"rejections_count": 4,
"rejections_period_days": 7,
"last_rejection_reason": "biometric_score_below_threshold"
}
}
}subject.profile.blacklisted
Se publica cuando un sujeto es agregado a la blacklist de un tenant, ya sea manualmente por un admin o automaticamente por el sistema de scoring.
| Campo | Valor |
|---|---|
| Routing key | subject.profile.blacklisted |
| Consumidores | ImagFlow, ImagLend |
| Trigger | Sujeto agregado a blacklist (manual o automatico) |
{
"event_id": "uuid",
"event_type": "subject.profile.blacklisted",
"version": "1.0",
"timestamp": "2026-05-18T10:30:00Z",
"correlation_id": "uuid",
"tenant_id": "uuid",
"source": "imagy-subject-360",
"actor_id": "admin-uuid",
"actor_type": "user",
"data": {
"subject_id": "uuid",
"identifier_type": "cedula",
"identifier_value": "1234567890",
"previous_status": "flagged",
"new_status": "blacklisted",
"list_entry_id": "uuid",
"reason": "Multiples intentos de fraude confirmados",
"added_by_type": "user",
"expires_at": null,
"trust_score": 12.00
}
}subject.device.new
Se publica cuando un sujeto accede desde un dispositivo que no habia sido registrado previamente en ese tenant.
| Campo | Valor |
|---|---|
| Routing key | subject.device.new |
| Consumidores | Audit |
| Trigger | Primer acceso desde dispositivo desconocido |
{
"event_id": "uuid",
"event_type": "subject.device.new",
"version": "1.0",
"timestamp": "2026-05-18T10:30:00Z",
"correlation_id": "uuid",
"tenant_id": "uuid",
"source": "imagy-subject-360",
"actor_type": "system",
"data": {
"subject_id": "uuid",
"identifier_type": "cedula",
"identifier_value": "1234567890",
"device_id": "uuid",
"device_fingerprint": "fp-xyz789",
"ip_address": "200.100.50.25",
"geo_country": "Colombia",
"geo_city": "Cali",
"user_agent": "Mozilla/5.0 (Linux; Android 14)",
"known_devices_count": 4,
"risk_indicators": {
"new_geo_location": true,
"different_country": false,
"vpn_detected": false
}
}
}Eventos Consumidos
ImagID consume eventos de todos los dominios de la plataforma para construir la vista 360 de cada sujeto. Cada evento consumido genera un registro en subject_events y puede disparar actualizaciones en metricas, trust score y alertas.
flow.execution.completed
| Productor | ImagFlow |
|---|---|
| Routing key | flow.execution.completed |
| Accion | Registrar interaccion exitosa, actualizar metricas, recalcular trust score |
Logica del consumidor:
- Buscar o crear
subject_profilesporsubject_identifier - Buscar o crear
subject_tenant_viewspara el tenant - Registrar evento en
subject_events - Incrementar
total_interactionsytotal_approvals(si result=approved) - Recalcular trust score (+5.0 por aprobacion)
- Registrar dispositivo si hay info de device
- Actualizar
last_interaction_atylast_seen_at
public class FlowExecutionCompletedConsumer : BaseConsumer<FlowExecutionCompleted>
{
protected override async Task HandleAsync(FlowExecutionCompleted message, CancellationToken ct)
{
// 1. Obtener o crear perfil global
var profile = await _profileService.GetOrCreateAsync(
message.Data.SubjectIdentifier, ct);
// 2. Obtener o crear vista del tenant
var tenantView = await _tenantViewService.GetOrCreateAsync(
message.TenantId, profile.Id, ct);
// 3. Registrar evento
await _eventService.RecordAsync(new SubjectEvent
{
TenantId = message.TenantId,
SubjectId = profile.Id,
EventType = message.EventType,
EventSource = message.Source,
SourceEventId = message.EventId,
EventData = message.Data,
EventTimestamp = message.Timestamp
}, ct);
// 4. Actualizar metricas
tenantView.TotalInteractions++;
if (message.Data.Result == "approved")
tenantView.TotalApprovals++;
// 5. Recalcular trust score
var scoreChange = message.Data.Result == "approved" ? 5.0m : 0m;
await _trustScoreService.AdjustAsync(
tenantView, scoreChange, ct);
// 6. Registrar dispositivo si aplica
if (message.Data.Metadata?.DeviceInfo != null)
{
await _deviceService.RegisterAccessAsync(
message.TenantId, profile.Id,
message.Data.Metadata.DeviceInfo, ct);
}
// 7. Actualizar timestamps
tenantView.LastInteractionAt = message.Timestamp;
profile.LastSeenAt = message.Timestamp;
await _unitOfWork.SaveChangesAsync(ct);
}
}flow.execution.failed
| Productor | ImagFlow |
|---|---|
| Routing key | flow.execution.failed |
| Accion | Registrar rechazo, actualizar metricas, penalizar trust score, evaluar patrones |
Logica del consumidor:
- Buscar o crear perfil y vista del tenant
- Registrar evento en
subject_events - Incrementar
total_interactionsytotal_rejections - Penalizar trust score (-10.0)
- Evaluar patron: si hay 3+ rechazos consecutivos, generar alerta y penalizar adicionalmente (-15.0)
- Si trust score cae bajo umbral critico (< 25), agregar automaticamente a watchlist
public class FlowExecutionFailedConsumer : BaseConsumer<FlowExecutionFailed>
{
protected override async Task HandleAsync(FlowExecutionFailed message, CancellationToken ct)
{
var profile = await _profileService.GetOrCreateAsync(
message.Data.SubjectIdentifier, ct);
var tenantView = await _tenantViewService.GetOrCreateAsync(
message.TenantId, profile.Id, ct);
await _eventService.RecordAsync(new SubjectEvent
{
TenantId = message.TenantId,
SubjectId = profile.Id,
EventType = message.EventType,
EventSource = message.Source,
SourceEventId = message.EventId,
EventData = message.Data,
EventTimestamp = message.Timestamp
}, ct);
tenantView.TotalInteractions++;
tenantView.TotalRejections++;
// Penalizar trust score
await _trustScoreService.AdjustAsync(tenantView, -10.0m, ct);
// Evaluar patron de rechazos consecutivos
var recentRejections = await _eventService.CountConsecutiveRejectionsAsync(
message.TenantId, profile.Id, ct);
if (recentRejections >= 3)
{
await _alertService.CreateAsync(new SubjectAlert
{
TenantId = message.TenantId,
SubjectId = profile.Id,
AlertType = "multiple_rejections",
Severity = "high",
Title = "Multiples rechazos consecutivos",
Description = $"{recentRejections} rechazos sin aprobacion intermedia",
AlertData = new { count = recentRejections, last_reason = message.Data.FailureReason }
}, ct);
await _trustScoreService.AdjustAsync(tenantView, -15.0m, ct);
}
// Evaluar umbral critico
if (tenantView.TrustScore < 25.0m)
{
await _listService.AddToWatchlistAsync(
message.TenantId, profile.Id,
"Trust score critico por rechazos", ct);
}
await _unitOfWork.SaveChangesAsync(ct);
}
}flow.step.completed
| Productor | ImagFlow |
|---|---|
| Routing key | flow.step.completed |
| Accion | Registrar evento granular, registrar dispositivo si aplica |
Logica del consumidor:
- Registrar evento en
subject_events(timeline granular) - Si el paso incluye info de dispositivo, registrar o actualizar en
subject_devices - Si es un dispositivo nuevo, evaluar si genera alerta y publicar
subject.device.new
public class FlowStepCompletedConsumer : BaseConsumer<FlowStepCompleted>
{
protected override async Task HandleAsync(FlowStepCompleted message, CancellationToken ct)
{
var profile = await _profileService.GetByIdentifierAsync(
message.Data.SubjectIdentifier, ct);
if (profile is null) return; // Perfil aun no existe, se creara con execution.completed
await _eventService.RecordAsync(new SubjectEvent
{
TenantId = message.TenantId,
SubjectId = profile.Id,
EventType = message.EventType,
EventSource = message.Source,
SourceEventId = message.EventId,
EventData = message.Data,
EventTimestamp = message.Timestamp
}, ct);
// Registrar dispositivo si hay info disponible
if (message.Data.DeviceInfo != null)
{
var isNew = await _deviceService.RegisterAccessAsync(
message.TenantId, profile.Id,
message.Data.DeviceInfo, ct);
if (isNew)
{
await _eventBus.PublishAsync(new SubjectDeviceNew
{
TenantId = message.TenantId,
SubjectId = profile.Id,
DeviceInfo = message.Data.DeviceInfo
}, ct);
}
}
await _unitOfWork.SaveChangesAsync(ct);
}
}lending.application.created
| Productor | ImagLend |
|---|---|
| Routing key | lending.application.created |
| Accion | Registrar inicio de proceso crediticio, vincular sujeto |
Logica del consumidor:
- Buscar o crear perfil global por documento del solicitante
- Buscar o crear vista del tenant
- Registrar evento en timeline
- Actualizar timestamps de interaccion
public class LendingApplicationCreatedConsumer : BaseConsumer<LendingApplicationCreated>
{
protected override async Task HandleAsync(LendingApplicationCreated message, CancellationToken ct)
{
var profile = await _profileService.GetOrCreateAsync(
new SubjectIdentifier
{
Type = message.Data.IdentifierType,
Value = message.Data.IdentifierValue,
FullName = message.Data.ApplicantName
}, ct);
var tenantView = await _tenantViewService.GetOrCreateAsync(
message.TenantId, profile.Id, ct);
await _eventService.RecordAsync(new SubjectEvent
{
TenantId = message.TenantId,
SubjectId = profile.Id,
EventType = message.EventType,
EventSource = message.Source,
SourceEventId = message.EventId,
EventData = message.Data,
EventTimestamp = message.Timestamp
}, ct);
tenantView.LastInteractionAt = message.Timestamp;
profile.LastSeenAt = message.Timestamp;
await _unitOfWork.SaveChangesAsync(ct);
}
}lending.credit.disbursed
| Productor | ImagLend |
|---|---|
| Routing key | lending.credit.disbursed |
| Accion | Registrar desembolso, mejorar trust score |
Logica del consumidor:
- Registrar evento en timeline
- Ajustar trust score positivamente (+3.0) — un desembolso indica confianza del tenant
- Actualizar metricas en
metrics_summary
public class LendingCreditDisbursedConsumer : BaseConsumer<LendingCreditDisbursed>
{
protected override async Task HandleAsync(LendingCreditDisbursed message, CancellationToken ct)
{
var profile = await _profileService.GetByIdentifierAsync(
message.Data.SubjectIdentifier, ct);
if (profile is null) return;
var tenantView = await _tenantViewService.GetAsync(
message.TenantId, profile.Id, ct);
await _eventService.RecordAsync(new SubjectEvent
{
TenantId = message.TenantId,
SubjectId = profile.Id,
EventType = message.EventType,
EventSource = message.Source,
SourceEventId = message.EventId,
EventData = message.Data,
EventTimestamp = message.Timestamp
}, ct);
// Desembolso indica confianza
await _trustScoreService.AdjustAsync(tenantView, 3.0m, ct);
await _unitOfWork.SaveChangesAsync(ct);
}
}lending.payment.received
| Productor | ImagLend |
|---|---|
| Routing key | lending.payment.received |
| Accion | Registrar pago, mejorar trust score levemente |
Logica del consumidor:
- Registrar evento en timeline
- Ajustar trust score positivamente (+2.0) — pagos puntuales mejoran confianza
public class LendingPaymentReceivedConsumer : BaseConsumer<LendingPaymentReceived>
{
protected override async Task HandleAsync(LendingPaymentReceived message, CancellationToken ct)
{
var profile = await _profileService.GetByIdentifierAsync(
message.Data.SubjectIdentifier, ct);
if (profile is null) return;
var tenantView = await _tenantViewService.GetAsync(
message.TenantId, profile.Id, ct);
await _eventService.RecordAsync(new SubjectEvent
{
TenantId = message.TenantId,
SubjectId = profile.Id,
EventType = message.EventType,
EventSource = message.Source,
SourceEventId = message.EventId,
EventData = message.Data,
EventTimestamp = message.Timestamp
}, ct);
await _trustScoreService.AdjustAsync(tenantView, 2.0m, ct);
await _unitOfWork.SaveChangesAsync(ct);
}
}lending.credit.defaulted
| Productor | ImagLend |
|---|---|
| Routing key | lending.credit.defaulted |
| Accion | Registrar default, penalizar trust score fuertemente, generar alerta |
Logica del consumidor:
- Registrar evento en timeline
- Incrementar
total_defaults - Penalizar trust score fuertemente (-25.0)
- Generar alerta de severidad alta
- Si es el segundo default, agregar automaticamente a blacklist
public class LendingCreditDefaultedConsumer : BaseConsumer<LendingCreditDefaulted>
{
protected override async Task HandleAsync(LendingCreditDefaulted message, CancellationToken ct)
{
var profile = await _profileService.GetByIdentifierAsync(
message.Data.SubjectIdentifier, ct);
if (profile is null) return;
var tenantView = await _tenantViewService.GetAsync(
message.TenantId, profile.Id, ct);
await _eventService.RecordAsync(new SubjectEvent
{
TenantId = message.TenantId,
SubjectId = profile.Id,
EventType = message.EventType,
EventSource = message.Source,
SourceEventId = message.EventId,
EventData = message.Data,
EventTimestamp = message.Timestamp
}, ct);
tenantView.TotalDefaults++;
// Penalizacion fuerte
await _trustScoreService.AdjustAsync(tenantView, -25.0m, ct);
// Generar alerta
await _alertService.CreateAsync(new SubjectAlert
{
TenantId = message.TenantId,
SubjectId = profile.Id,
AlertType = "default_pattern",
Severity = "high",
Title = "Credito en default",
Description = $"Default #{tenantView.TotalDefaults} registrado",
AlertData = new { credit_id = message.Data.CreditId, amount = message.Data.Amount }
}, ct);
// Segundo default: blacklist automatica
if (tenantView.TotalDefaults >= 2)
{
await _listService.AddToBlacklistAsync(
message.TenantId, profile.Id,
"Multiples creditos en default", ct);
tenantView.Status = "blacklisted";
await _eventBus.PublishAsync(new SubjectProfileBlacklisted
{
TenantId = message.TenantId,
SubjectId = profile.Id,
Reason = "Multiples creditos en default"
}, ct);
}
await _unitOfWork.SaveChangesAsync(ct);
}
}sign.signature.completed
| Productor | ImagSign |
|---|---|
| Routing key | sign.signature.completed |
| Accion | Registrar firma en timeline, asociar dispositivo |
Logica del consumidor:
- Registrar evento en timeline
- Si incluye info de dispositivo, registrar acceso
- Actualizar timestamps
public class SignatureCompletedConsumer : BaseConsumer<SignatureCompleted>
{
protected override async Task HandleAsync(SignatureCompleted message, CancellationToken ct)
{
var profile = await _profileService.GetByIdentifierAsync(
message.Data.SignerIdentifier, ct);
if (profile is null) return;
await _eventService.RecordAsync(new SubjectEvent
{
TenantId = message.TenantId,
SubjectId = profile.Id,
EventType = message.EventType,
EventSource = message.Source,
SourceEventId = message.EventId,
EventData = message.Data,
EventTimestamp = message.Timestamp
}, ct);
if (message.Data.DeviceInfo != null)
{
await _deviceService.RegisterAccessAsync(
message.TenantId, profile.Id,
message.Data.DeviceInfo, ct);
}
profile.LastSeenAt = message.Timestamp;
await _unitOfWork.SaveChangesAsync(ct);
}
}identity.user.created
| Productor | Identity |
|---|---|
| Routing key | identity.user.created |
| Accion | Crear o vincular perfil de sujeto, crear vista del tenant |
Logica del consumidor:
- Buscar perfil existente por documento. Si no existe, crear uno nuevo.
- Crear
subject_tenant_viewspara el tenant con score inicial (50.0) - Actualizar datos de contacto en el perfil si son mas recientes
public class IdentityUserCreatedConsumer : BaseConsumer<IdentityUserCreated>
{
protected override async Task HandleAsync(IdentityUserCreated message, CancellationToken ct)
{
// Crear o vincular perfil global
var profile = await _profileService.GetOrCreateAsync(
new SubjectIdentifier
{
Type = message.Data.IdentifierType,
Value = message.Data.IdentifierValue,
FullName = message.Data.FullName,
Email = message.Data.Email,
Phone = message.Data.Phone
}, ct);
// Crear vista del tenant con score inicial
await _tenantViewService.GetOrCreateAsync(
message.TenantId, profile.Id, ct);
// Actualizar datos de contacto si son mas recientes
if (message.Data.Email != null)
profile.Email = message.Data.Email;
if (message.Data.Phone != null)
profile.Phone = message.Data.Phone;
profile.LastSeenAt = message.Timestamp;
await _unitOfWork.SaveChangesAsync(ct);
}
}Esquema de Routing
Eventos Producidos
| Routing Key | Exchange | Queue destino |
|---|---|---|
subject.profile.flagged | imagy.events | imagy-flow-subject-events, imagy-lending-subject-events |
subject.profile.blacklisted | imagy.events | imagy-flow-subject-events, imagy-lending-subject-events |
subject.device.new | imagy.events | imagy-audit-subject-events |
Eventos Consumidos
| Routing Key | Exchange | Queue |
|---|---|---|
flow.execution.completed | imagy.events | imagy-subject-flow-events |
flow.execution.failed | imagy.events | imagy-subject-flow-events |
flow.step.completed | imagy.events | imagy-subject-flow-events |
lending.application.created | imagy.events | imagy-subject-lending-events |
lending.credit.disbursed | imagy.events | imagy-subject-lending-events |
lending.payment.received | imagy.events | imagy-subject-lending-events |
lending.credit.defaulted | imagy.events | imagy-subject-lending-events |
sign.signature.completed | imagy.events | imagy-subject-sign-events |
identity.user.created | imagy.events | imagy-subject-identity-events |
Garantias de Procesamiento
- Idempotencia: Cada evento se registra con
source_event_idunico. Si se recibe duplicado, se ignora. - Orden: No se garantiza orden estricto. El sistema es tolerante a eventos fuera de orden gracias a timestamps originales.
- Retry: Eventos fallidos van a dead-letter queue con retry exponencial (3 intentos, backoff 5s/30s/120s).
- Observabilidad: Cada consumidor emite metricas de procesamiento (latencia, errores, eventos/segundo).