Events & Callbacks
Handle geotrigger events with webhooks, real-time updates, and custom callback functions for seamless integration.
Event Handling & Callbacks
Geotriggers generate events when users enter, exit, or dwell within defined boundaries. Learn how to handle these events with webhooks, real-time streams, and custom callbacks for seamless integration with your application.
Event Types
Understanding the different types of events that geotriggers can generate:
// Core event types
const eventTypes = {
ENTER: 'geotrigger.enter',
EXIT: 'geotrigger.exit',
DWELL: 'geotrigger.dwell',
// Extended events
APPROACH: 'geotrigger.approach',
DEPART: 'geotrigger.depart',
REVISIT: 'geotrigger.revisit',
// System events
ACTIVATED: 'geotrigger.activated',
DEACTIVATED: 'geotrigger.deactivated',
ERROR: 'geotrigger.error'
};
// Event data structure
interface GeotriggerEvent {
id: string;
type: keyof typeof eventTypes;
triggerId: string;
userId: string;
timestamp: string;
location: {
lat: number;
lng: number;
accuracy: number;
};
metadata: {
dwellTime?: number;
visitCount?: number;
deviceInfo: {
type: 'ios' | 'android' | 'web';
version: string;
userAgent?: string;
};
custom?: Record<string, any>;
};
}Webhook Configuration
Set up webhooks to receive real-time geotrigger events:
import { OMXClient } from '@omx-sdk/geotrigger';
const omx = new OMXClient({
clientId: 'your_client_id',
secretKey: 'your_secret_key'
});
// Configure webhook endpoint
const webhook = await omx.webhook.create({
name: 'Geotrigger Events Handler',
url: 'https://yourapp.com/webhooks/geotrigger',
events: [
'geotrigger.enter',
'geotrigger.exit',
'geotrigger.dwell'
],
// Security settings
secret: 'webhook_secret_key',
signatureHeader: 'X-OMX-Signature',
// Retry configuration
retryPolicy: {
maxRetries: 3,
backoffMultiplier: 2,
initialDelay: 1000
},
// Filtering
filters: {
triggerIds: ['trigger_123', 'trigger_456'],
userSegments: ['premium_customers'],
minimumAccuracy: 50 // meters
}
});
// Subscribe specific triggers to webhooks
await omx.geotrigger.subscribeToWebhook('trigger_123', webhook.id);Event Listeners
Register event listeners for real-time event processing:
// Register event listeners
omx.geotrigger.on('enter', async (event) => {
console.log(`User ${event.userId} entered trigger ${event.triggerId}`);
// Send welcome notification
await omx.pushNotification.send({
userId: event.userId,
title: 'Welcome!',
body: 'You're near our store. Check out today's deals!',
data: { triggerId: event.triggerId }
});
});
omx.geotrigger.on('exit', async (event) => {
console.log(`User ${event.userId} exited trigger ${event.triggerId}`);
// Send feedback request
await omx.email.send({
to: event.userId,
template: 'feedback_request',
data: {
visitDuration: event.metadata.dwellTime,
location: event.triggerId
}
});
});
omx.geotrigger.on('dwell', async (event) => {
console.log(`User ${event.userId} dwelling in trigger ${event.triggerId}`);
// Trigger special offer for long stays
if (event.metadata.dwellTime > 300) { // 5 minutes
await omx.campaign.trigger({
campaignId: 'long_stay_discount',
userId: event.userId,
context: event
});
}
});
// Error handling
omx.geotrigger.on('error', (error) => {
console.error('Geotrigger error:', error);
// Send to error tracking service
Sentry.captureException(error);
});Real-time Event Streaming
Stream events in real-time using WebSockets or Server-Sent Events:
// WebSocket connection for real-time events
const eventStream = omx.geotrigger.stream({
triggers: ['trigger_123', 'trigger_456'],
events: ['enter', 'exit'],
// Connection options
reconnect: true,
heartbeat: 30000, // 30 seconds
// Event filtering
filters: {
userSegments: ['premium_customers'],
timeWindow: {
start: '09:00',
end: '21:00',
timezone: 'America/New_York'
}
}
});
// Handle incoming events
eventStream.on('event', (event) => {
console.log('Real-time event:', event);
updateUI(event);
});
eventStream.on('connect', () => {
console.log('Connected to event stream');
});
eventStream.on('disconnect', () => {
console.log('Disconnected from event stream');
});
// Server-Sent Events alternative
const sseConnection = omx.geotrigger.streamSSE({
endpoint: '/events/geotrigger',
triggers: ['trigger_123']
});
sseConnection.addEventListener('geotrigger.enter', (event) => {
const data = JSON.parse(event.data);
handleEnterEvent(data);
});Custom Callback Functions
Define custom business logic for specific geotrigger events:
// Define custom callback functions
const customCallbacks = {
loyaltyProgram: async (event) => {
if (event.type === 'enter') {
// Award loyalty points for store visits
await awardLoyaltyPoints(event.userId, {
points: 10,
reason: 'Store visit',
locationId: event.triggerId
});
// Check for nearby friends
const nearbyFriends = await findNearbyFriends(
event.userId,
event.location,
1000 // 1km radius
);
if (nearbyFriends.length > 0) {
await omx.pushNotification.send({
userId: event.userId,
title: 'Friends Nearby!',
body: `${nearbyFriends.length} of your friends are nearby`,
data: { friends: nearbyFriends }
});
}
}
},
dynamicPricing: async (event) => {
if (event.type === 'dwell' && event.metadata.dwellTime > 180) {
// Implement dynamic pricing based on dwell time
const currentOccupancy = await getCurrentStoreOccupancy(event.triggerId);
const priceMultiplier = calculatePriceMultiplier(currentOccupancy);
await updateDynamicPricing(event.triggerId, priceMultiplier);
}
},
analyticsTracker: async (event) => {
// Send to analytics platform
await analytics.track('Geotrigger Event', {
eventType: event.type,
triggerId: event.triggerId,
userId: event.userId,
timestamp: event.timestamp,
location: event.location,
dwellTime: event.metadata.dwellTime,
visitCount: event.metadata.visitCount
});
}
};
// Register callbacks with triggers
await omx.geotrigger.registerCallback('trigger_123', [
customCallbacks.loyaltyProgram,
customCallbacks.analyticsTracker
]);
await omx.geotrigger.registerCallback('trigger_456', [
customCallbacks.dynamicPricing,
customCallbacks.analyticsTracker
]);Conditional Event Processing
Process events based on specific conditions and business rules:
// Define conditional processors
const conditionalProcessors = [
{
name: 'VIP Customer Handler',
condition: (event) => {
return event.metadata.custom?.customerTier === 'VIP';
},
handler: async (event) => {
// Special VIP treatment
await omx.pushNotification.send({
userId: event.userId,
title: 'VIP Welcome',
body: 'Welcome back! Your personal assistant will be with you shortly.',
priority: 'high'
});
// Notify staff
await notifyStaff({
message: `VIP customer ${event.userId} has arrived`,
location: event.triggerId
});
}
},
{
name: 'Repeat Visitor Bonus',
condition: (event) => {
return event.type === 'enter' &&
event.metadata.visitCount > 5 &&
getDaysSinceLastVisit(event.userId, event.triggerId) > 30;
},
handler: async (event) => {
// Welcome back bonus for returning customers
await omx.campaign.trigger({
campaignId: 'welcome_back_bonus',
userId: event.userId,
data: {
visitCount: event.metadata.visitCount,
bonusAmount: Math.min(event.metadata.visitCount * 5, 50)
}
});
}
},
{
name: 'Competitor Proximity Alert',
condition: async (event) => {
if (event.type !== 'exit') return false;
// Check if user is heading towards competitor
const nearbyCompetitors = await findNearbyCompetitors(
event.location,
500 // 500m radius
);
return nearbyCompetitors.length > 0;
},
handler: async (event) => {
// Send retention offer
await omx.pushNotification.send({
userId: event.userId,
title: 'Wait! Special Offer Just for You',
body: 'Get 15% off your next purchase if you return within 1 hour',
data: {
offerCode: generateOfferCode(),
expiresAt: Date.now() + 3600000 // 1 hour
}
});
}
}
];
// Register conditional processors
await omx.geotrigger.registerConditionalProcessors(conditionalProcessors);Event Batching and Processing
Handle high-volume events efficiently with batching and queuing:
// Configure event batching
const batchProcessor = omx.geotrigger.createBatchProcessor({
batchSize: 100,
batchTimeout: 5000, // 5 seconds
processor: async (events) => {
console.log(`Processing batch of ${events.length} events`);
// Group events by type
const groupedEvents = events.reduce((acc, event) => {
if (!acc[event.type]) acc[event.type] = [];
acc[event.type].push(event);
return acc;
}, {});
// Process each event type
await Promise.all([
processEnterEvents(groupedEvents.enter || []),
processExitEvents(groupedEvents.exit || []),
processDwellEvents(groupedEvents.dwell || [])
]);
},
errorHandler: (error, events) => {
console.error('Batch processing error:', error);
// Retry failed events individually
events.forEach(event => {
eventQueue.push(event);
});
}
});
// Queue configuration for high availability
const eventQueue = omx.geotrigger.createQueue({
name: 'geotrigger-events',
concurrency: 10,
// Redis configuration for distributed processing
redis: {
host: 'redis.yourapp.com',
port: 6379,
keyPrefix: 'geotrigger:'
},
// Dead letter queue for failed events
deadLetterQueue: {
enabled: true,
maxRetries: 5,
retryDelay: 30000 // 30 seconds
}
});
// Process queued events
eventQueue.process(async (job) => {
const event = job.data;
try {
await processGeotriggerEvent(event);
return { status: 'success', eventId: event.id };
} catch (error) {
throw new Error(`Failed to process event ${event.id}: ${error.message}`);
}
});Event Filtering and Deduplication
Implement sophisticated filtering to reduce noise and prevent duplicate processing:
// Advanced event filtering
const eventFilter = omx.geotrigger.createFilter({
// Duplicate detection
deduplication: {
enabled: true,
timeWindow: 30000, // 30 seconds
keyGenerator: (event) => `${event.userId}:${event.triggerId}:${event.type}`
},
// Accuracy filtering
accuracyFilter: {
maxRadius: 100, // meters
rejectLowAccuracy: true
},
// Time-based filtering
timeFilter: {
businessHours: {
enabled: true,
start: '09:00',
end: '21:00',
timezone: 'America/New_York',
weekdays: [1, 2, 3, 4, 5, 6] // Mon-Sat
},
// Rate limiting per user
rateLimit: {
perUser: {
maxEvents: 10,
timeWindow: 3600000 // 1 hour
},
perTrigger: {
maxEvents: 100,
timeWindow: 60000 // 1 minute
}
}
},
// Custom filters
customFilters: [
// Filter out rapid enter/exit events (possibly GPS noise)
(event, previousEvent) => {
if (!previousEvent) return true;
const timeDiff = new Date(event.timestamp) - new Date(previousEvent.timestamp);
const isOppositeEvent = (
(event.type === 'enter' && previousEvent.type === 'exit') ||
(event.type === 'exit' && previousEvent.type === 'enter')
);
return !(isOppositeEvent && timeDiff < 10000); // 10 seconds
},
// Filter events from test users
(event) => {
return !event.metadata.custom?.isTestUser;
}
]
});
// Apply filters to event stream
omx.geotrigger.applyFilter(eventFilter);Error Handling and Monitoring
Implement robust error handling and monitoring for event processing:
// Error handling strategies
const errorHandler = {
// Webhook delivery failures
webhookError: async (error, event, webhook) => {
console.error(`Webhook delivery failed: ${error.message}`);
// Implement exponential backoff
const retryDelay = Math.pow(2, webhook.retryCount) * 1000;
if (webhook.retryCount < 3) {
setTimeout(() => {
omx.webhook.retry(webhook.id, event);
}, retryDelay);
} else {
// Send to dead letter queue
await omx.deadLetterQueue.add({
type: 'webhook_failure',
webhookId: webhook.id,
event: event,
error: error.message
});
}
},
// Callback execution failures
callbackError: async (error, event, callbackName) => {
console.error(`Callback '${callbackName}' failed: ${error.message}`);
// Log to monitoring service
await monitoring.logError({
type: 'callback_error',
callback: callbackName,
event: event,
error: error,
timestamp: new Date().toISOString()
});
// Alert development team for critical callbacks
if (callbackName === 'paymentProcessor') {
await alerting.sendAlert({
severity: 'critical',
message: `Payment callback failed for event ${event.id}`,
context: { event, error }
});
}
}
};
// Performance monitoring
const performanceMonitor = omx.geotrigger.createPerformanceMonitor({
metrics: [
'event_processing_time',
'webhook_delivery_time',
'callback_execution_time',
'event_throughput'
],
thresholds: {
processingTime: 1000, // ms
deliveryTime: 5000, // ms
errorRate: 0.05 // 5%
},
alerts: {
email: 'alerts@yourapp.com',
slack: 'webhook_url_here'
}
});
// Health checks
setInterval(async () => {
const health = await omx.geotrigger.checkEventProcessingHealth();
if (!health.healthy) {
console.warn('Event processing health check failed:', health.issues);
await performanceMonitor.alert(health);
}
}, 60000); // Every minuteBest Practices
- Event Filtering: Implement robust filtering to reduce noise and processing overhead
- Idempotency: Ensure event handlers are idempotent to handle duplicate events safely
- Error Recovery: Implement retry logic with exponential backoff for transient failures
- Monitoring: Set up comprehensive monitoring and alerting for event processing
- Performance: Use batching and queuing for high-volume event processing
- Security: Validate webhook signatures and authenticate event sources