Skip to main content

Descripción General

Los eventos de webhook de entidades le permiten recibir notificaciones en tiempo real cuando se crean, actualizan entidades (personas, empresas, dispositivos, etc.) o su estado cambia en la plataforma Gu1. Estos eventos le permiten mantener sus sistemas sincronizados con Gu1 y automatizar flujos de trabajo basados en cambios en el ciclo de vida de las entidades.

¿Por Qué Usar Eventos de Entidades?

Sincronización en Tiempo Real

Mantenga su base de datos sincronizada con los datos de entidades de Gu1

Flujos de Trabajo Automatizados

Active acciones cuando el estado de la entidad cambie

Registro de Auditoría

Rastree todos los cambios de entidades para cumplimiento

Eficiente

No es necesario consultar la API para actualizaciones

Eventos Disponibles

entity.created

Se activa cuando se crea una nueva entidad en Gu1. Cuándo se dispara:
  • Se crea una nueva persona, empresa, dispositivo u otra entidad a través de POST /entities
Filtros disponibles:
  • entityTypes: Solo recibe eventos para tipos de entidades específicos (ej., ["person", "company"])

entity.updated

Se activa cuando se actualizan los datos de una entidad (excluyendo cambios de estado). Cuándo se dispara:
  • Se actualiza la información de la entidad a través de PATCH /entities/:id
  • Cambios en nombre, atributos, datos de entidad, ID fiscal, etc.
Nota: Los cambios de estado activan entity.status_changed en su lugar. Filtros disponibles:
  • entityTypes: Solo recibe eventos para tipos de entidades específicos

entity.status_changed

Se activa cuando cambia el estado de una entidad. Cuándo se dispara:
  • Transiciones de estado de entidad (ej., under_reviewactive, activeblocked)
  • Actualizaciones de estado a través de PATCH /entities/:id o acciones de cumplimiento automatizadas
Filtros disponibles:
  • entityTypes: Filtrar por tipo de entidad
  • statusChanges.from: Solo activar cuando cambie DESDE un estado específico
  • statusChanges.to: Solo activar cuando cambie A un estado específico

Ejemplos de Payload de Eventos

entity.created

{
  "event": "entity.created",
  "timestamp": "2025-01-07T10:00:00.000Z",
  "payload": {
    "entity": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "externalId": "customer_abc123",
      "name": "John Doe",
      "type": "person",
      "taxId": "123-45-6789",
      "countryCode": "US",
      "status": "under_review",
      "isClient": false,
      "attributes": {
        "email": "john.doe@example.com",
        "phone": "+1234567890",
        "customerTier": "premium"
      },
      "entityData": {
        "person": {
          "firstName": "John",
          "lastName": "Doe",
          "dateOfBirth": "1990-01-15",
          "nationality": "US",
          "occupation": "Software Engineer"
        }
      },
      "createdAt": "2025-01-07T10:00:00.000Z",
      "updatedAt": "2025-01-07T10:00:00.000Z"
    },
    "createdBy": "user_xyz789",
    "metadata": {
      "source": "api",
      "userAgent": "Mozilla/5.0...",
      "ipAddress": "192.168.1.100"
    }
  }
}
Campos Clave:
  • entity: Objeto de entidad completo con todos los datos
  • entity.externalId: Su identificador único para la entidad
  • entity.type: Tipo de entidad (person, company, device, etc.)
  • entity.status: Estado actual (under_review, active, blocked, etc.)
  • createdBy: ID del usuario que creó la entidad
  • metadata: Contexto adicional sobre la creación

entity.updated

{
  "event": "entity.updated",
  "timestamp": "2025-01-07T10:05:00.000Z",
  "payload": {
    "entity": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "externalId": "customer_abc123",
      "name": "John Doe",
      "type": "person",
      "taxId": "123-45-6789",
      "countryCode": "US",
      "status": "active",
      "isClient": false,
      "attributes": {
        "email": "john.doe@newemail.com",
        "phone": "+1234567890",
        "customerTier": "premium"
      },
      "entityData": {
        "person": {
          "firstName": "John",
          "lastName": "Doe",
          "dateOfBirth": "1990-01-15",
          "nationality": "US",
          "occupation": "Senior Software Engineer"
        }
      },
      "updatedAt": "2025-01-07T10:05:00.000Z"
    },
    "changes": {
      "attributes.email": {
        "old": "john.doe@example.com",
        "new": "john.doe@newemail.com"
      },
      "entityData.person.occupation": {
        "old": "Software Engineer",
        "new": "Senior Software Engineer"
      }
    },
    "updatedBy": "user_xyz789",
    "reason": "Customer provided updated information"
  }
}
Campos Clave:
  • entity: Objeto de entidad completo con datos actualizados
  • changes: Objeto que muestra qué cambió (valores antiguos vs nuevos)
  • updatedBy: ID del usuario que actualizó la entidad
  • reason: Razón opcional para la actualización

entity.status_changed

{
  "event": "entity.status_changed",
  "timestamp": "2025-01-07T10:10:00.000Z",
  "payload": {
    "status": "active",
    "previousStatus": "under_review",
    "reason": "KYC verification completed successfully",
    "entity": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "externalId": "customer_abc123",
      "name": "John Doe",
      "type": "person",
      "taxId": "123-45-6789",
      "countryCode": "US",
      "status": "active",
      "updatedAt": "2025-01-07T10:10:00.000Z"
    }
  }
}
Campos Clave:
  • status: Nuevo estado
  • previousStatus: Estado anterior
  • reason: Por qué cambió el estado
  • entity: Objeto de entidad completo

Configuración de Filtros

Filtrar por Tipo de Entidad

Solo reciba eventos para tipos de entidades específicos:
{
  "eventTypes": ["entity.created", "entity.updated"],
  "filters": {
    "entityTypes": ["person", "company"]
  }
}
Esta configuración solo activará webhooks para entidades de persona y empresa, ignorando dispositivos y otros tipos.

Filtrar por Cambio de Estado

Solo reciba eventos cuando el estado de la entidad cambie a valores específicos:
{
  "eventTypes": ["entity.status_changed"],
  "filters": {
    "entityTypes": ["person"],
    "statusChanges": {
      "to": "blocked"
    }
  }
}
Esto solo se activará cuando una entidad de persona sea cambiada A estado blocked. Filtrar cuando cambie DESDE un estado específico:
{
  "eventTypes": ["entity.status_changed"],
  "filters": {
    "statusChanges": {
      "from": "active",
      "to": "suspended"
    }
  }
}
Esto solo se activará cuando el estado cambie de active a suspended.

Ejemplos de Código

Node.js - Manejo de Eventos de Entidades

const express = require('express');
const app = express();

app.use(express.json({
  verify: (req, res, buf) => {
    req.rawBody = buf.toString('utf8');
  }
}));

app.post('/webhooks/entity', async (req, res) => {
  try {
    // Verificar firma (ver guía de seguridad)
    if (!verifySignature(req.rawBody, req.headers['x-webhook-signature'])) {
      return res.status(401).json({ error: 'Invalid signature' });
    }

    const { event, payload } = req.body;

    // Redirigir al controlador apropiado
    switch (event) {
      case 'entity.created':
        await handleEntityCreated(payload);
        break;

      case 'entity.updated':
        await handleEntityUpdated(payload);
        break;

      case 'entity.status_changed':
        await handleEntityStatusChanged(payload);
        break;

      default:
        console.warn('Unknown event type:', event);
    }

    res.status(200).json({ success: true });
  } catch (error) {
    console.error('Webhook error:', error);
    res.status(500).json({ error: error.message });
  }
});

async function handleEntityCreated(payload) {
  const { entity, createdBy, metadata } = payload;

  console.log(`Entity created: ${entity.name} (${entity.type})`);

  // Sincronizar con su base de datos
  await db.entities.create({
    data: {
      gu1Id: entity.id,
      externalId: entity.externalId,
      name: entity.name,
      type: entity.type,
      status: entity.status,
      data: entity.entityData,
      attributes: entity.attributes,
      createdBy,
      metadata
    }
  });

  // Activar flujos de trabajo basados en tipo de entidad
  if (entity.type === 'person') {
    await sendWelcomeEmail(entity.attributes.email);
  }
}

async function handleEntityUpdated(payload) {
  const { entity, changes, updatedBy, reason } = payload;

  console.log(`Entity updated: ${entity.name}`);
  console.log('Changes:', changes);

  // Actualizar su base de datos
  await db.entities.update({
    where: { gu1Id: entity.id },
    data: {
      name: entity.name,
      status: entity.status,
      data: entity.entityData,
      attributes: entity.attributes,
      lastUpdatedBy: updatedBy,
      updatedAt: new Date()
    }
  });

  // Registrar cambios para registro de auditoría
  await db.entityChanges.create({
    data: {
      entityId: entity.id,
      changes,
      changedBy: updatedBy,
      reason,
      timestamp: new Date()
    }
  });
}

async function handleEntityStatusChanged(payload) {
  const { entity, status, previousStatus, reason } = payload;

  console.log(`Entity status changed: ${previousStatus}${status}`);

  // Actualizar base de datos
  await db.entities.update({
    where: { gu1Id: entity.id },
    data: {
      status,
      statusChangedAt: new Date()
    }
  });

  // Tomar acción basada en nuevo estado
  switch (status) {
    case 'active':
      await activateCustomerAccount(entity.externalId);
      await sendNotification(entity, 'Your account has been activated');
      break;

    case 'blocked':
      await deactivateCustomerAccount(entity.externalId);
      await sendNotification(entity, 'Your account has been blocked');
      break;

    case 'suspended':
      await suspendCustomerAccount(entity.externalId);
      await sendNotification(entity, 'Your account has been suspended');
      break;
  }

  // Registrar cambio de estado
  await db.statusHistory.create({
    data: {
      entityId: entity.id,
      fromStatus: previousStatus,
      toStatus: status,
      reason,
      timestamp: new Date()
    }
  });
}

app.listen(3000);

Python - Manejo de Eventos de Entidades

from flask import Flask, request, jsonify
import logging

app = Flask(__name__)

@app.route('/webhooks/entity', methods=['POST'])
def entity_webhook():
    try:
        # Verificar firma (ver guía de seguridad)
        signature = request.headers.get('X-Webhook-Signature')
        raw_body = request.get_data(as_text=True)

        if not verify_signature(raw_body, signature):
            return jsonify({'error': 'Invalid signature'}), 401

        # Analizar webhook
        payload = request.json
        event = payload['event']
        data = payload['payload']

        # Redirigir al controlador
        handlers = {
            'entity.created': handle_entity_created,
            'entity.updated': handle_entity_updated,
            'entity.status_changed': handle_entity_status_changed
        }

        handler = handlers.get(event)
        if handler:
            handler(data)
        else:
            logging.warning(f'Unknown event type: {event}')

        return jsonify({'success': True}), 200

    except Exception as e:
        logging.error(f'Webhook error: {e}')
        return jsonify({'error': str(e)}), 500

def handle_entity_created(data):
    entity = data['entity']
    created_by = data['createdBy']

    logging.info(f"Entity created: {entity['name']} ({entity['type']})")

    # Sincronizar con base de datos
    db.entities.insert({
        'gu1_id': entity['id'],
        'external_id': entity['externalId'],
        'name': entity['name'],
        'type': entity['type'],
        'status': entity['status'],
        'data': entity['entityData'],
        'attributes': entity['attributes'],
        'created_by': created_by
    })

    # Enviar correo de bienvenida para personas
    if entity['type'] == 'person' and 'email' in entity['attributes']:
        send_welcome_email(entity['attributes']['email'])

def handle_entity_updated(data):
    entity = data['entity']
    changes = data['changes']
    updated_by = data.get('updatedBy')
    reason = data.get('reason')

    logging.info(f"Entity updated: {entity['name']}")

    # Actualizar base de datos
    db.entities.update(
        {'gu1_id': entity['id']},
        {
            'name': entity['name'],
            'status': entity['status'],
            'data': entity['entityData'],
            'attributes': entity['attributes'],
            'last_updated_by': updated_by
        }
    )

    # Registrar cambios
    db.entity_changes.insert({
        'entity_id': entity['id'],
        'changes': changes,
        'changed_by': updated_by,
        'reason': reason
    })

def handle_entity_status_changed(data):
    entity = data['entity']
    status = data['status']
    previous_status = data['previousStatus']
    reason = data.get('reason')

    logging.info(f"Status changed: {previous_status}{status}")

    # Actualizar base de datos
    db.entities.update(
        {'gu1_id': entity['id']},
        {'status': status}
    )

    # Tomar acción basada en estado
    if status == 'active':
        activate_customer_account(entity['externalId'])
        send_notification(entity, 'Your account has been activated')
    elif status == 'blocked':
        deactivate_customer_account(entity['externalId'])
        send_notification(entity, 'Your account has been blocked')

    # Registrar cambio de estado
    db.status_history.insert({
        'entity_id': entity['id'],
        'from_status': previous_status,
        'to_status': status,
        'reason': reason
    })

if __name__ == '__main__':
    app.run(port=3000)

Casos de Uso

Caso de Uso 1: Sincronización de Base de Datos en Tiempo Real

Mantenga su base de datos local sincronizada con Gu1:
async function syncEntityToDatabase(entity) {
  await db.customers.upsert({
    where: { gu1Id: entity.id },
    update: {
      name: entity.name,
      status: entity.status,
      data: entity.entityData,
      attributes: entity.attributes,
      syncedAt: new Date()
    },
    create: {
      gu1Id: entity.id,
      externalId: entity.externalId,
      name: entity.name,
      type: entity.type,
      status: entity.status,
      data: entity.entityData,
      attributes: entity.attributes,
      syncedAt: new Date()
    }
  });
}

Caso de Uso 2: Activación Automática de Cuenta

Active automáticamente cuentas de clientes cuando el estado cambie a active:
async function handleEntityStatusChanged(payload) {
  const { entity, status, previousStatus } = payload;

  if (status === 'active' && previousStatus === 'under_review') {
    // Habilitar inicio de sesión
    await auth.enableUser(entity.externalId);

    // Otorgar acceso a servicios
    await services.grantAccess(entity.externalId);

    // Enviar correo de bienvenida
    await email.send({
      to: entity.attributes.email,
      subject: 'Welcome! Your account is now active',
      template: 'account-activated',
      data: { name: entity.name }
    });

    // Registrar evento
    console.log(`Account activated: ${entity.name}`);
  }
}

Caso de Uso 3: Monitoreo de Cumplimiento

Rastree y responda a cambios de estado de entidades para cumplimiento:
async function handleEntityStatusChanged(payload) {
  const { entity, status, previousStatus, reason } = payload;

  // Alertar al equipo de cumplimiento cuando una entidad es bloqueada
  if (status === 'blocked') {
    await slack.send({
      channel: '#compliance-alerts',
      message: `🚨 Entity blocked: ${entity.name} (${entity.externalId})`,
      fields: {
        'Entity ID': entity.id,
        'Previous Status': previousStatus,
        'Reason': reason,
        'Blocked At': new Date().toISOString()
      }
    });

    // Crear caso en sistema de cumplimiento
    await compliance.createCase({
      entityId: entity.id,
      type: 'blocked_entity',
      priority: 'high',
      data: entity
    });
  }
}

Caso de Uso 4: Notificaciones a Clientes

Notifique a los clientes cuando su información cambie:
async function handleEntityUpdated(payload) {
  const { entity, changes } = payload;

  // Verificar si se actualizó el correo
  if (changes['attributes.email']) {
    await sendEmail({
      to: changes['attributes.email'].new,
      subject: 'Email address updated',
      body: 'Your email address has been updated. If this was not you, please contact support.'
    });

    // También enviar al correo antiguo
    await sendEmail({
      to: changes['attributes.email'].old,
      subject: 'Email address changed',
      body: 'Your email address has been changed. If this was not you, please contact support immediately.'
    });
  }

  // Notificar en cambios de campos importantes
  const importantFields = ['taxId', 'countryCode', 'name'];
  const importantChanges = Object.keys(changes).some(key =>
    importantFields.some(field => key.includes(field))
  );

  if (importantChanges) {
    await sendSecurityAlert(entity, changes);
  }
}

Mejores Prácticas

El entity.externalId es su identificador único. Úselo para buscar entidades en su base de datos:
const customer = await db.customers.findUnique({
  where: { externalId: entity.externalId }
});
Siempre almacene el ID de entidad de Gu1 en su base de datos para referencia:
await db.customers.create({
  data: {
    externalId: entity.externalId,
    gu1Id: entity.id, // Almacenar esto
    name: entity.name
  }
});
Incluso si solo se suscribe a eventos específicos, maneje todos los tipos de eventos con gracia:
switch (event) {
  case 'entity.created':
    await handleEntityCreated(payload);
    break;

  default:
    console.warn('Unhandled event type:', event);
    // Aún así devolver 200
}
Mantenga un registro de auditoría de todos los cambios de entidades:
await db.auditLog.create({
  data: {
    entityId: entity.id,
    event,
    changes,
    timestamp: new Date(),
    webhook: req.body
  }
});
Use el ID de entidad y timestamp para prevenir procesamiento duplicado:
const webhookId = `${entity.id}_${event}_${timestamp}`;
const processed = await db.webhookLog.findUnique({
  where: { webhookId }
});

if (processed) {
  return; // Omitir duplicado
}
Configure filtros para recibir solo eventos relevantes:
{
  "eventTypes": ["entity.status_changed"],
  "filters": {
    "entityTypes": ["person"],
    "statusChanges": {
      "to": "active"
    }
  }
}

Solución de Problemas

Verificar:
  • El webhook está suscrito al evento entity.created
  • El tipo de entidad coincide con sus filtros (si están configurados)
  • El webhook está habilitado en el dashboard
  • El endpoint es públicamente accesible
Probar:
# Crear una entidad de prueba
curl -X POST https://api.gu1.io/entities \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{"name":"Test","type":"person"}'
El objeto changes solo incluye campos que realmente cambiaron. Si no ve un campo, significa que no se actualizó.Ejemplo:
{
  "changes": {
    "name": {
      "old": "John",
      "new": "John Doe"
    }
  }
}
Solo cambió name, otros campos permanecen igual.
Verificar:
  • El estado realmente cambió (no solo se actualizó la entidad)
  • Los filtros coinciden con el cambio de estado (from/to)
  • El cambio de estado no está siendo filtrado
Ejemplo de filtro que podría bloquear eventos:
{
  "filters": {
    "statusChanges": {
      "to": "blocked"
    }
  }
}
Esto SOLO se disparará cuando el estado cambie A blocked.

Próximos Pasos