Documentation Index Fetch the complete documentation index at: https://docs.gu1.ai/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Entity webhook events allow you to receive real-time notifications when entities (persons, companies, devices, etc.) are created, updated, or their status changes in the Gu1 platform. These events enable you to keep your systems synchronized with Gu1 and automate workflows based on entity lifecycle changes.
Why Use Entity Events?
Real-Time Sync Keep your database synchronized with Gu1 entity data
Automated Workflows Trigger actions when entity status changes
Audit Trail Track all entity changes for compliance
Efficient No need to poll the API for updates
Available Events
entity.created
Triggered when a new entity is created in Gu1.
When it fires :
A new person, company, device, or other entity is created via POST /entities
Filters available :
entityTypes: Only receive events for specific entity types (e.g., ["person", "company"])
entity.updated
Triggered when an entity’s data is updated (excluding status changes).
When it fires :
Entity information is updated via PATCH /entities/:id
Changes to name, attributes, entity data, tax ID, etc.
Note : Status changes trigger entity.status_changed instead.
Filters available :
entityTypes: Only receive events for specific entity types
entity.status_changed
Triggered when an entity’s status changes.
When it fires :
Entity status transitions (e.g., under_review → active, active → blocked)
Status updates via PATCH /entities/:id or automated compliance actions
Filters available :
entityTypes: Filter by entity type
statusChanges.from: Only trigger when changing FROM specific status
statusChanges.to: Only trigger when changing TO specific status
Event Payload Examples
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"
}
}
}
Key Fields :
entity: Complete entity object with all data
entity.externalId: Your unique identifier for the entity
entity.type: Entity type (person, company, device, etc.)
entity.status: Current status (under_review, active, blocked, etc.)
createdBy: User ID who created the entity
metadata: Additional context about the creation
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"
}
}
Key Fields :
entity: Complete entity object with updated data
changes: Object showing what changed (old vs new values)
updatedBy: User ID who updated the entity
reason: Optional reason for the update
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"
}
}
}
Key Fields :
status: New status
previousStatus: Previous status
reason: Why the status changed
entity: Complete entity object
Filter Configuration
Filter by Entity Type
Only receive events for specific entity types:
{
"eventTypes" : [ "entity.created" , "entity.updated" ],
"filters" : {
"entityTypes" : [ "person" , "company" ]
}
}
This configuration will only trigger webhooks for person and company entities, ignoring devices and other types.
Filter by Status Change
Only receive events when entity status changes to specific values:
{
"eventTypes" : [ "entity.status_changed" ],
"filters" : {
"entityTypes" : [ "person" ],
"statusChanges" : {
"to" : "blocked"
}
}
}
This will only trigger when a person entity is changed TO blocked status.
Filter when changing FROM a specific status :
{
"eventTypes" : [ "entity.status_changed" ],
"filters" : {
"statusChanges" : {
"from" : "active" ,
"to" : "suspended"
}
}
}
This will only trigger when status changes from active to suspended.
Code Examples
Node.js - Handling Entity Events
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 {
// Verify signature (see security guide)
if ( ! verifySignature ( req . rawBody , req . headers [ 'x-webhook-signature' ])) {
return res . status ( 401 ). json ({ error: 'Invalid signature' });
}
const { event , payload } = req . body ;
// Route to appropriate handler
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 } )` );
// Sync to your database
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
}
});
// Trigger workflows based on entity type
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 );
// Update your database
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 ()
}
});
// Log changes for audit trail
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 } ` );
// Update database
await db . entities . update ({
where: { gu1Id: entity . id },
data: {
status ,
statusChangedAt: new Date ()
}
});
// Take action based on new status
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 ;
}
// Log status change
await db . statusHistory . create ({
data: {
entityId: entity . id ,
fromStatus: previousStatus ,
toStatus: status ,
reason ,
timestamp: new Date ()
}
});
}
app . listen ( 3000 );
Python - Handling Entity Events
from flask import Flask, request, jsonify
import logging
app = Flask( __name__ )
@app.route ( '/webhooks/entity' , methods = [ 'POST' ])
def entity_webhook ():
try :
# Verify signature (see security guide)
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
# Parse webhook
payload = request.json
event = payload[ 'event' ]
data = payload[ 'payload' ]
# Route to handler
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' ] } )" )
# Sync to database
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
})
# Send welcome email for persons
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' ] } " )
# Update database
db.entities.update(
{ 'gu1_id' : entity[ 'id' ]},
{
'name' : entity[ 'name' ],
'status' : entity[ 'status' ],
'data' : entity[ 'entityData' ],
'attributes' : entity[ 'attributes' ],
'last_updated_by' : updated_by
}
)
# Log changes
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 } " )
# Update database
db.entities.update(
{ 'gu1_id' : entity[ 'id' ]},
{ 'status' : status}
)
# Take action based on status
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' )
# Log status change
db.status_history.insert({
'entity_id' : entity[ 'id' ],
'from_status' : previous_status,
'to_status' : status,
'reason' : reason
})
if __name__ == '__main__' :
app.run( port = 3000 )
Use Cases
Use Case 1: Real-Time Database Sync
Keep your local database synchronized with 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 ()
}
});
}
Use Case 2: Automated Account Activation
Automatically activate customer accounts when status changes to active:
async function handleEntityStatusChanged ( payload ) {
const { entity , status , previousStatus } = payload ;
if ( status === 'active' && previousStatus === 'under_review' ) {
// Enable login
await auth . enableUser ( entity . externalId );
// Grant access to services
await services . grantAccess ( entity . externalId );
// Send welcome email
await email . send ({
to: entity . attributes . email ,
subject: 'Welcome! Your account is now active' ,
template: 'account-activated' ,
data: { name: entity . name }
});
// Log event
console . log ( `Account activated: ${ entity . name } ` );
}
}
Use Case 3: Compliance Monitoring
Track and respond to entity status changes for compliance:
async function handleEntityStatusChanged ( payload ) {
const { entity , status , previousStatus , reason } = payload ;
// Alert compliance team when entity is blocked
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 ()
}
});
// Create case in compliance system
await compliance . createCase ({
entityId: entity . id ,
type: 'blocked_entity' ,
priority: 'high' ,
data: entity
});
}
}
Use Case 4: Customer Notifications
Notify customers when their information changes:
async function handleEntityUpdated ( payload ) {
const { entity , changes } = payload ;
// Check if email was updated
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.'
});
// Also send to old email
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.'
});
}
// Notify on important field changes
const importantFields = [ 'taxId' , 'countryCode' , 'name' ];
const importantChanges = Object . keys ( changes ). some ( key =>
importantFields . some ( field => key . includes ( field ))
);
if ( importantChanges ) {
await sendSecurityAlert ( entity , changes );
}
}
Best Practices
Use externalId for Lookups
The entity.externalId is your unique identifier. Use it to look up entities in your database: const customer = await db . customers . findUnique ({
where: { externalId: entity . externalId }
});
Always store the Gu1 entity ID in your database for reference: await db . customers . create ({
data: {
externalId: entity . externalId ,
gu1Id: entity . id , // Store this
name: entity . name
}
});
Even if you only subscribe to specific events, handle all event types gracefully: switch ( event ) {
case 'entity.created' :
await handleEntityCreated ( payload );
break ;
default :
console . warn ( 'Unhandled event type:' , event );
// Still return 200
}
Keep an audit trail of all entity changes: await db . auditLog . create ({
data: {
entityId: entity . id ,
event ,
changes ,
timestamp: new Date (),
webhook: req . body
}
});
Use the entity ID and timestamp to prevent duplicate processing: const webhookId = ` ${ entity . id } _ ${ event } _ ${ timestamp } ` ;
const processed = await db . webhookLog . findUnique ({
where: { webhookId }
});
if ( processed ) {
return ; // Skip duplicate
}
Use Filters to Reduce Noise
Configure filters to only receive relevant events: {
"eventTypes" : [ "entity.status_changed" ],
"filters" : {
"entityTypes" : [ "person" ],
"statusChanges" : {
"to" : "active"
}
}
}
Troubleshooting
Not Receiving entity.created Events
Check :
Webhook is subscribed to entity.created event
Entity type matches your filters (if configured)
Webhook is enabled in dashboard
Endpoint is publicly accessible
Test :# Create a test entity
curl -X POST https://api.gu1.io/entities \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"name":"Test","type":"person"}'
Missing Changes in entity.updated
The changes object only includes fields that actually changed. If you don’t see a field, it means it wasn’t updated. Example :{
"changes" : {
"name" : {
"old" : "John" ,
"new" : "John Doe"
}
}
}
Only name changed, other fields remain the same.
entity.status_changed Not Firing
Check :
Status actually changed (not just entity updated)
Filters match the status change (from/to)
Status change is not being filtered out
Example filter that might block events :{
"filters" : {
"statusChanges" : {
"to" : "blocked"
}
}
}
This will ONLY fire when status changes TO blocked.
Next Steps
KYC Events Handle KYC verification events
Rule Events Process compliance rule triggers
Webhook Security Secure your webhook endpoints
Configuration Configure webhook settings