Skip to content

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.

CampoValor
Routing keysubject.profile.flagged
ConsumidoresImagFlow, ImagLend
TriggerAlert Service detecta patron y cambia status a flagged
json
{
  "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.

CampoValor
Routing keysubject.profile.blacklisted
ConsumidoresImagFlow, ImagLend
TriggerSujeto agregado a blacklist (manual o automatico)
json
{
  "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.

CampoValor
Routing keysubject.device.new
ConsumidoresAudit
TriggerPrimer acceso desde dispositivo desconocido
json
{
  "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

ProductorImagFlow
Routing keyflow.execution.completed
AccionRegistrar interaccion exitosa, actualizar metricas, recalcular trust score

Logica del consumidor:

  1. Buscar o crear subject_profiles por subject_identifier
  2. Buscar o crear subject_tenant_views para el tenant
  3. Registrar evento en subject_events
  4. Incrementar total_interactions y total_approvals (si result=approved)
  5. Recalcular trust score (+5.0 por aprobacion)
  6. Registrar dispositivo si hay info de device
  7. Actualizar last_interaction_at y last_seen_at
csharp
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

ProductorImagFlow
Routing keyflow.execution.failed
AccionRegistrar rechazo, actualizar metricas, penalizar trust score, evaluar patrones

Logica del consumidor:

  1. Buscar o crear perfil y vista del tenant
  2. Registrar evento en subject_events
  3. Incrementar total_interactions y total_rejections
  4. Penalizar trust score (-10.0)
  5. Evaluar patron: si hay 3+ rechazos consecutivos, generar alerta y penalizar adicionalmente (-15.0)
  6. Si trust score cae bajo umbral critico (< 25), agregar automaticamente a watchlist
csharp
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

ProductorImagFlow
Routing keyflow.step.completed
AccionRegistrar evento granular, registrar dispositivo si aplica

Logica del consumidor:

  1. Registrar evento en subject_events (timeline granular)
  2. Si el paso incluye info de dispositivo, registrar o actualizar en subject_devices
  3. Si es un dispositivo nuevo, evaluar si genera alerta y publicar subject.device.new
csharp
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

ProductorImagLend
Routing keylending.application.created
AccionRegistrar inicio de proceso crediticio, vincular sujeto

Logica del consumidor:

  1. Buscar o crear perfil global por documento del solicitante
  2. Buscar o crear vista del tenant
  3. Registrar evento en timeline
  4. Actualizar timestamps de interaccion
csharp
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

ProductorImagLend
Routing keylending.credit.disbursed
AccionRegistrar desembolso, mejorar trust score

Logica del consumidor:

  1. Registrar evento en timeline
  2. Ajustar trust score positivamente (+3.0) — un desembolso indica confianza del tenant
  3. Actualizar metricas en metrics_summary
csharp
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

ProductorImagLend
Routing keylending.payment.received
AccionRegistrar pago, mejorar trust score levemente

Logica del consumidor:

  1. Registrar evento en timeline
  2. Ajustar trust score positivamente (+2.0) — pagos puntuales mejoran confianza
csharp
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

ProductorImagLend
Routing keylending.credit.defaulted
AccionRegistrar default, penalizar trust score fuertemente, generar alerta

Logica del consumidor:

  1. Registrar evento en timeline
  2. Incrementar total_defaults
  3. Penalizar trust score fuertemente (-25.0)
  4. Generar alerta de severidad alta
  5. Si es el segundo default, agregar automaticamente a blacklist
csharp
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

ProductorImagSign
Routing keysign.signature.completed
AccionRegistrar firma en timeline, asociar dispositivo

Logica del consumidor:

  1. Registrar evento en timeline
  2. Si incluye info de dispositivo, registrar acceso
  3. Actualizar timestamps
csharp
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

ProductorIdentity
Routing keyidentity.user.created
AccionCrear o vincular perfil de sujeto, crear vista del tenant

Logica del consumidor:

  1. Buscar perfil existente por documento. Si no existe, crear uno nuevo.
  2. Crear subject_tenant_views para el tenant con score inicial (50.0)
  3. Actualizar datos de contacto en el perfil si son mas recientes
csharp
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 KeyExchangeQueue destino
subject.profile.flaggedimagy.eventsimagy-flow-subject-events, imagy-lending-subject-events
subject.profile.blacklistedimagy.eventsimagy-flow-subject-events, imagy-lending-subject-events
subject.device.newimagy.eventsimagy-audit-subject-events

Eventos Consumidos

Routing KeyExchangeQueue
flow.execution.completedimagy.eventsimagy-subject-flow-events
flow.execution.failedimagy.eventsimagy-subject-flow-events
flow.step.completedimagy.eventsimagy-subject-flow-events
lending.application.createdimagy.eventsimagy-subject-lending-events
lending.credit.disbursedimagy.eventsimagy-subject-lending-events
lending.payment.receivedimagy.eventsimagy-subject-lending-events
lending.credit.defaultedimagy.eventsimagy-subject-lending-events
sign.signature.completedimagy.eventsimagy-subject-sign-events
identity.user.createdimagy.eventsimagy-subject-identity-events

Garantias de Procesamiento

  • Idempotencia: Cada evento se registra con source_event_id unico. 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).

Reimagine Tech LLC — Documentacion Interna