POS Webhook Integration Guide for EmiliaVision¶
Version: 2.0
Last Updated: November 2025
Reading Time: 10 minutes
🎯 Why This Integration Matters¶
EmiliaVision combines AI-powered video analytics with your POS data to provide unprecedented insights into restaurant operations. By integrating your POS system via webhooks, you'll unlock:
- Real-time table turnover analytics - Know exactly how long tables are occupied
- Staff performance metrics - Track service speed and efficiency by server
- Revenue optimization - Correlate visual patterns with sales data
- Kitchen timing insights - Understand order flow and preparation times
🚀 Integration Overview - Choose Your Path¶
We offer two integration strategies, each with distinct benefits:
Option A: Real-Time Updates (Recommended for Maximum Value)¶
Send webhooks as events happen throughout the dining experience - ✅ Best for: Complete operational visibility - ✅ Data richness: Captures every update with timestamps - ✅ Implementation: 2-3 webhook calls per table session
Option B: End-of-Session (Quickest to Implement)¶
Send one webhook when the check is closed - ✅ Best for: Quick integration, basic analytics - ✅ Data richness: Final totals and summary - ✅ Implementation: 1 webhook call per table session
📦 What Data We Need (And Why)¶
Critical Data (Must Have)¶
{
"table_number": "Mesa 8", // Essential for video correlation
"session_start": "2025-11-24T18:30:00-03:00", // When table was opened
"session_end": "2025-11-24T20:15:00-03:00", // When check was closed
"order_id": "ORD-2025-0124" // Your unique identifier
}
Valuable Data (Strongly Recommended)¶
{
"server": {
"opened_by": {"id": 72, "name": "João Silva"}, // Who started service
"closed_by": {"id": 96, "name": "Maria Santos"} // Who closed check
},
"items": [
{
"timestamp": "2025-11-24T18:35:00-03:00", // When item was ordered
"employee_id": 72, // Who took the order
"name": "Focaccia",
"quantity": 1,
"price": 19.00
}
],
"totals": {
"subtotal": 194.00,
"tip": 25.22,
"tax": 19.40,
"total": 238.62
}
}
🔄 Integration Patterns¶
Pattern A: Real-Time Updates (Maximum Value)¶
Send webhooks at key moments during the dining experience:
1. Table Opened (Session Start)¶
{
"event": "session_opened",
"timestamp": "2025-11-24T18:30:00-03:00",
"order_id": "ORD-2025-0124",
"table_number": "Mesa 8",
"server": {"id": 72, "name": "João Silva"}
}
2. Items Ordered (Each Update)¶
{
"event": "items_added",
"timestamp": "2025-11-24T18:35:00-03:00",
"order_id": "ORD-2025-0124",
"table_number": "Mesa 8",
"items": [
{
"timestamp": "2025-11-24T18:35:00-03:00",
"employee_id": 72,
"name": "Focaccia",
"quantity": 1,
"price": 19.00
},
{
"timestamp": "2025-11-24T18:35:00-03:00",
"employee_id": 72,
"name": "Agua com Gas",
"quantity": 2,
"price": 11.00
}
]
}
3. Payment Completed (Session End)¶
{
"event": "session_closed",
"timestamp": "2025-11-24T20:15:00-03:00",
"order_id": "ORD-2025-0124",
"table_number": "Mesa 8",
"session_duration_minutes": 105,
"payment": {
"subtotal": 194.00,
"tip": 25.22,
"tax": 19.40,
"total": 238.62,
"method": "VISA_CREDIT",
"closed_by": {"id": 96, "name": "Maria Santos"}
},
"fiscal_receipt": "NFe35251133111140001168650020000762241540579084"
}
Pattern B: End-of-Session Only (Quick Start)¶
Send everything in one webhook after payment:
{
"event": "session_complete",
"order_id": "ORD-2025-0124",
"table_number": "Mesa 8",
"session_start": "2025-11-24T18:30:00-03:00",
"session_end": "2025-11-24T20:15:00-03:00",
"opened_by": {"id": 72, "name": "João Silva"},
"closed_by": {"id": 96, "name": "Maria Santos"},
"items": [
{
"name": "Focaccia",
"quantity": 1,
"price": 19.00,
"timestamp": "2025-11-24T18:35:00-03:00",
"employee_id": 72
},
{
"name": "Agua com Gas",
"quantity": 2,
"price": 11.00,
"timestamp": "2025-11-24T18:35:00-03:00",
"employee_id": 72
},
{
"name": "Carbonara",
"quantity": 1,
"price": 85.00,
"timestamp": "2025-11-24T18:45:00-03:00",
"employee_id": 72
},
{
"name": "Tiramisu",
"quantity": 2,
"price": 36.00,
"timestamp": "2025-11-24T19:50:00-03:00",
"employee_id": 96
}
],
"totals": {
"subtotal": 194.00,
"tip": 25.22,
"tax": 19.40,
"total": 238.62
},
"payment": {
"method": "VISA_CREDIT",
"fiscal_receipt": "NFe35251133111140001168650020000762241540579084"
}
}
🛠️ Technical Implementation¶
Your Webhook Endpoint¶
- YOUR_TOKEN: Unique identifier provided by EmiliaVision
- Method: POST
- Content-Type: application/json
- Max Payload Size: 10MB (typically < 50KB)
Authentication¶
Your webhook token serves as authentication. Keep it secure:
Environments¶
- Production:
https://api.emiliavision.com/webhooks/v1/pos/{token} - Testing: Add
?env=devto the URL
Response Handling¶
Success Response (200):
{
"status": "success",
"event_id": "019ab6ed-150a-7be1-920f-ab82305e5d41",
"message": "Webhook received and stored",
"received_at": "2025-11-24T18:30:00.123Z"
}
💡 Implementation Examples¶
Example 1: Python Integration¶
import requests
import json
from datetime import datetime
class EmiliaVisionWebhook:
def __init__(self, token, environment='prod'):
self.token = token
self.base_url = f"https://api.emiliavision.com/webhooks/v1/pos/{token}"
if environment == 'dev':
self.base_url += "?env=dev"
def send_table_opened(self, order_id, table_number, server):
"""Send webhook when table is opened"""
payload = {
"event": "session_opened",
"timestamp": datetime.now().isoformat(),
"order_id": order_id,
"table_number": table_number,
"server": server
}
return self._send(payload)
def send_items_added(self, order_id, table_number, items):
"""Send webhook when items are added to order"""
payload = {
"event": "items_added",
"timestamp": datetime.now().isoformat(),
"order_id": order_id,
"table_number": table_number,
"items": items
}
return self._send(payload)
def send_session_closed(self, order_data):
"""Send webhook when check is closed"""
payload = {
"event": "session_closed",
"timestamp": datetime.now().isoformat(),
**order_data
}
return self._send(payload)
def _send(self, payload):
try:
response = requests.post(
self.base_url,
json=payload,
timeout=10
)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Webhook error: {e}")
# Implement retry logic here
return None
# Usage
webhook = EmiliaVisionWebhook("YOUR_TOKEN_HERE", environment='dev')
# When table opens
webhook.send_table_opened(
order_id="ORD-2025-0124",
table_number="Mesa 8",
server={"id": 72, "name": "João Silva"}
)
# When items are added
webhook.send_items_added(
order_id="ORD-2025-0124",
table_number="Mesa 8",
items=[
{
"timestamp": datetime.now().isoformat(),
"employee_id": 72,
"name": "Focaccia",
"quantity": 1,
"price": 19.00
}
]
)
Example 2: Node.js Integration¶
const axios = require('axios');
class EmiliaVisionWebhook {
constructor(token, environment = 'prod') {
this.token = token;
this.baseUrl = `https://api.emiliavision.com/webhooks/v1/pos/${token}`;
if (environment === 'dev') {
this.baseUrl += '?env=dev';
}
}
async sendTableOpened(orderId, tableNumber, server) {
const payload = {
event: 'session_opened',
timestamp: new Date().toISOString(),
order_id: orderId,
table_number: tableNumber,
server: server
};
return this.send(payload);
}
async sendItemsAdded(orderId, tableNumber, items) {
const payload = {
event: 'items_added',
timestamp: new Date().toISOString(),
order_id: orderId,
table_number: tableNumber,
items: items
};
return this.send(payload);
}
async send(payload) {
try {
const response = await axios.post(this.baseUrl, payload);
return response.data;
} catch (error) {
console.error('Webhook error:', error.message);
// Implement retry logic
throw error;
}
}
}
// Usage
const webhook = new EmiliaVisionWebhook('YOUR_TOKEN_HERE', 'dev');
// When table opens
await webhook.sendTableOpened(
'ORD-2025-0124',
'Mesa 8',
{ id: 72, name: 'João Silva' }
);
✅ Best Practices¶
1. Include Timestamps for Everything¶
{
"items": [
{
"timestamp": "2025-11-24T18:35:00-03:00", // ✅ GOOD
"employee_id": 72,
"name": "Focaccia"
}
]
}
2. Use Consistent Table Identifiers¶
3. Include Employee IDs When Available¶
4. Implement Retry Logic¶
import time
def send_with_retry(webhook_func, payload, max_retries=3):
for attempt in range(max_retries):
try:
return webhook_func(payload)
except Exception as e:
if attempt < max_retries - 1:
wait_time = 2 ** attempt # Exponential backoff
time.sleep(wait_time)
else:
raise
5. Send Test Data First¶
Always test with development environment before production:
curl -X POST \
"https://api.emiliavision.com/webhooks/v1/pos/YOUR_TOKEN?env=dev" \
-H "Content-Type: application/json" \
-d '{"event": "test", "table_number": "Mesa Test"}'
📊 What You'll Get Back¶
Once integrated, you'll have access to:
- Real-time Dashboard - Monitor tables, servers, and revenue live
- Performance Reports - Daily/weekly/monthly analytics
- Staff Insights - Server efficiency and table turnover metrics
- Revenue Optimization - Identify peak times and bottlenecks
- Custom Alerts - Notifications for long wait times or idle tables
🏃 Quick Start Checklist¶
Start simple, then add more data:
Phase 1: Basic Integration (Day 1)¶
- Request webhook token from EmiliaVision
- Send test webhook with table number and session times
- Verify data appears in dashboard
Phase 2: Enhanced Data (Week 1)¶
- Add item-level details with timestamps
- Include employee/server information
- Add payment and tip data
Phase 3: Real-time Updates (Week 2)¶
- Implement session_opened events
- Send items_added updates
- Complete with session_closed events
🆘 Support & Next Steps¶
Getting Started¶
- Email [email protected] for your webhook token
- Use our test environment to validate your integration
- Schedule a review call with our integration team
- Go live with production data
Need Help?¶
- Technical Support: [email protected]
- Integration Team: [email protected]
- Documentation: https://docs.emiliavision.com
- Status Page: https://status.emiliavision.com
Response Time¶
- Integration setup: < 24 hours
- Technical support: < 4 hours
- Token generation: Immediate
📝 Appendix: Complete Payload Reference¶
Comprehensive Session Payload (All Fields)¶
{
"event": "session_complete",
"timestamp": "2025-11-24T20:15:00-03:00",
"order_id": "ORD-2025-0124",
"table_number": "Mesa 8",
"session_start": "2025-11-24T18:30:00-03:00",
"session_end": "2025-11-24T20:15:00-03:00",
"duration_minutes": 105,
"server": {
"opened_by": {
"id": 72,
"name": "João Silva"
},
"closed_by": {
"id": 96,
"name": "Maria Santos"
},
"transfers": []
},
"items": [
{
"timestamp": "2025-11-24T18:35:00-03:00",
"employee_id": 72,
"employee_name": "João Silva",
"item_id": "ITEM-001",
"name": "Focaccia",
"category": "Appetizers",
"quantity": 1,
"unit_price": 19.00,
"total_price": 19.00,
"modifiers": [],
"notes": ""
},
{
"timestamp": "2025-11-24T18:35:00-03:00",
"employee_id": 72,
"employee_name": "João Silva",
"item_id": "ITEM-002",
"name": "Agua com Gas",
"category": "Beverages",
"quantity": 2,
"unit_price": 11.00,
"total_price": 22.00,
"modifiers": [],
"notes": ""
},
{
"timestamp": "2025-11-24T18:45:00-03:00",
"employee_id": 72,
"employee_name": "João Silva",
"item_id": "ITEM-003",
"name": "Carbonara Classica",
"category": "Pasta",
"quantity": 1,
"unit_price": 85.00,
"total_price": 85.00,
"modifiers": ["Extra Bacon"],
"notes": "No pepper"
},
{
"timestamp": "2025-11-24T19:50:00-03:00",
"employee_id": 96,
"employee_name": "Maria Santos",
"item_id": "ITEM-004",
"name": "Tiramisu",
"category": "Desserts",
"quantity": 2,
"unit_price": 36.00,
"total_price": 72.00,
"modifiers": [],
"notes": ""
}
],
"totals": {
"subtotal": 198.00,
"discounts": 0.00,
"tax": 19.80,
"service_charge": 0.00,
"tip": {
"amount": 25.74,
"percentage": 13.0
},
"total": 243.54
},
"payment": {
"method": "VISA_CREDIT",
"amount": 243.54,
"transaction_id": "TXN-2025-5678",
"approval_code": "123456",
"fiscal_receipt": "NFe35251133111140001168650020000762241540579084"
},
"customer": {
"count": 4,
"loyalty_id": null
},
"restaurant": {
"name": "Restaurant Name",
"location": "Main Branch",
"terminal_id": "POS-01"
}
}
Remember: Start simple, iterate quickly. You can begin with basic data and enhance over time. The most important thing is to start sending webhooks - even minimal data provides valuable insights!