Skip to content

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:

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
}
{
  "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

POST https://api.emiliavision.com/webhooks/v1/pos/{YOUR_TOKEN}
  • 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:

# Example token
019ab6ed-150a-7be1-920f-ab82305e5d41

Environments

  • Production: https://api.emiliavision.com/webhooks/v1/pos/{token}
  • Testing: Add ?env=dev to 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

{
  "table_number": "Mesa 8"    // Always use the same format
}

3. Include Employee IDs When Available

{
  "employee_id": 72,           // Helps track server performance
  "employee_name": "João Silva"
}

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:

  1. Real-time Dashboard - Monitor tables, servers, and revenue live
  2. Performance Reports - Daily/weekly/monthly analytics
  3. Staff Insights - Server efficiency and table turnover metrics
  4. Revenue Optimization - Identify peak times and bottlenecks
  5. 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

  1. Email [email protected] for your webhook token
  2. Use our test environment to validate your integration
  3. Schedule a review call with our integration team
  4. Go live with production data

Need Help?

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!