> For the complete documentation index, see [llms.txt](https://integrate.lexamica.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://integrate.lexamica.com/example-integrations/2b.-receive-cases-polling.md).

# Receive Cases via Polling

Poll for new cases when you can't receive webhooks.

***

## 🎯 Overview

> **📌 TL;DR**
>
> If your system can't receive inbound webhooks (firewall, no public endpoint), poll the API instead. Same data, you just pull it on your schedule.

**This guide is for you if:**

* You receive referrals from other firms via Lexamica
* You can't expose a public HTTPS endpoint (firewall restrictions)
* You prefer batch processing or scheduled syncs

**What you'll build:**

* A Case mapping to receive data in your format
* A stored event subscription for the `Case Received` event
* A polling loop that fetches new cases

**What this does NOT cover:**

* Accepting/declining invitations via API
* Sending status updates via API
* Real-time notifications

For webhooks instead, see [Receive Cases via Webhook](/example-integrations/2a.-receive-cases-webhook.md)

***

## 📖 Key Terms

| Term                          | Definition                                                            |
| ----------------------------- | --------------------------------------------------------------------- |
| **Stored Event Subscription** | Configuration that tells Lexamica to store events for later retrieval |
| **Stored Event**              | An event record stored in the database, waiting to be polled          |
| **Polling**                   | Periodically calling the API to retrieve new events                   |

***

## ⚙️ Architecture

```
Receive Cases via Polling
─────────────────────────

┌─────────────────┐     ┌─────────────────┐
│    Lexamica     │     │   Your System   │
│                 │     │                 │
│ Case sent to    │     │ Every 5 min:    │
│ your firm       │     │ GET /stored-    │
│       │         │     │ events          │
│       ▼         │     │       │         │
│ ┌───────────┐   │     │       ▼         │
│ │  Stored   │◄──┼─────┼─── Fetch new    │
│ │  Events   │   │     │    events       │
│ │  Database │   │     │       │         │
│ └───────────┘   │     │       ▼         │
│                 │     │ Process &       │
│                 │     │ store locally   │
└─────────────────┘     └─────────────────┘
```

> **🎯 Key Takeaway**
>
> Events are stored until you retrieve them. Poll as frequently as your use case requires—every minute for near-real-time, or hourly for batch processing.

***

## 📋 Prerequisites

**Credentials:**

* [ ] Organization ID, Public Key, and Private Key from Lexamica

**Infrastructure:**

* [ ] Ability to run scheduled jobs (cron, Task Scheduler, etc.)

**Foundational Docs:**

* [ ] [Organizations and Authentication](/1.-organizations-and-authentication.md) — understand your API keys
* [ ] [Mapping Engine](/2.-mapping-engine.md) — how field transformations work
* [ ] [Event Storage and Polling](/5.-event-storage-polling.md) — polling for events from Lexamica

***

## 💡 Step-by-Step Implementation

### Step 1: Create Your Case Mapping

Create a mapping that transforms Lexamica's Case fields to your format.

**Request:**

```bash
curl -X POST \
  "https://integration.lexamica.com/organization/{orgId}/mapping/create" \
  -H "Authorization: Bearer your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Inbound Case Mapping",
    "modelName": "Case",
    "fieldMappings": [
      {
        "lexamicaField": "_id",
        "foreignField": "lexamica_case_id",
        "lexamicaFieldType": "String",
        "foreignFieldType": "String"
      },
      {
        "lexamicaField": "client.firstName",
        "foreignField": "client_first_name",
        "lexamicaFieldType": "String",
        "foreignFieldType": "String"
      },
      {
        "lexamicaField": "client.lastName",
        "foreignField": "client_last_name",
        "lexamicaFieldType": "String",
        "foreignFieldType": "String"
      },
      {
        "lexamicaField": "client.email",
        "foreignField": "client_email",
        "lexamicaFieldType": "String",
        "foreignFieldType": "String"
      },
      {
        "lexamicaField": "client.phoneNumber",
        "foreignField": "client_phone",
        "lexamicaFieldType": "String",
        "foreignFieldType": "String"
      },
      {
        "lexamicaField": "caseType",
        "foreignField": "practice_area",
        "lexamicaFieldType": "String",
        "foreignFieldType": "String"
      },
      {
        "lexamicaField": "incident.date",
        "foreignField": "incident_date",
        "lexamicaFieldType": "Date",
        "foreignFieldType": "String"
      },
      {
        "lexamicaField": "incident.synopsis",
        "foreignField": "case_description",
        "lexamicaFieldType": "String",
        "foreignFieldType": "String"
      }
    ]
  }'
```

### Step 2: Create a Stored Event Subscription

Subscribe to the `Case Received` event for storage.

**Request:**

```bash
curl -X POST \
  "https://integration.lexamica.com/organization/{orgId}/stored-event-subscriptions/create" \
  -H "Authorization: Bearer your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "Case Received",
    "mapping": "64f1a2b3c4d5e6f7a8b9c0d2",
    "description": "Store incoming cases for polling",
    "active": true
  }'
```

### Step 3: Poll for Events

Query the stored events endpoint to retrieve new cases.

**Request:**

```bash
curl -X GET \
  "https://integration.lexamica.com/organization/{orgId}/stored-events?event=Case%20Received&from=2026-01-15T00:00:00Z&to=2026-01-15T23:59:59Z&limit=100" \
  -H "Authorization: Bearer your_private_key"
```

**Response:**

```json
{
  "events": [
    {
      "_id": "64f1a2b3c4d5e6f7a8b9c0d8",
      "event": "Case Received",
      "payload": {
        "lexamica_case_id": "64f1a2b3c4d5e6f7a8b9c0d3",
        "client_first_name": "Jane",
        "client_last_name": "Smith",
        "client_email": "jane@example.com",
        "client_phone": "555-123-4567",
        "practice_area": "Personal Injury",
        "incident_date": "2026-01-10",
        "case_description": "Rear-end collision on Highway 101."
      },
      "created": "2026-01-15T10:30:00.000Z"
    }
  ],
  "total": 1,
  "limit": 100,
  "skip": 0
}
```

***

## 🔧 Complete Code Example

### Node.js Polling Service

```javascript
const axios = require('axios');

// Configuration
const config = {
  orgId: process.env.LEXAMICA_ORG_ID,
  privateKey: process.env.LEXAMICA_PRIVATE_KEY,
  baseUrl: 'https://integration.lexamica.com'
};

class CasePoller {
  constructor() {
    this.lastPollTime = null;
  }

  async poll() {
    const now = new Date();
    
    // Default to last 24 hours on first run
    const from = this.lastPollTime || new Date(now.getTime() - 24 * 60 * 60 * 1000);
    
    try {
      const events = await this.fetchEvents(from, now);
      
      if (events.length > 0) {
        console.log(`Found ${events.length} new case(s)`);
        await this.processEvents(events);
      } else {
        console.log('No new cases');
      }
      
      // Update last poll time only after successful processing
      this.lastPollTime = now;
      
    } catch (error) {
      console.error('Polling failed:', error.message);
      // Don't update lastPollTime so we retry this window
    }
  }

  async fetchEvents(from, to) {
    const params = new URLSearchParams({
      event: 'Case Received',
      from: from.toISOString(),
      to: to.toISOString(),
      limit: '100'
    });

    const response = await axios.get(
      `${config.baseUrl}/organization/${config.orgId}/stored-events?${params}`,
      {
        headers: { Authorization: `Bearer ${config.privateKey}` }
      }
    );

    return response.data.events;
  }

  async processEvents(events) {
    for (const event of events) {
      await this.handleCaseReceived(event.payload);
    }
  }

  async handleCaseReceived(caseData) {
    console.log('Processing case:', caseData.lexamica_case_id);
    
    // Check if we've already processed this case
    const exists = await this.caseExists(caseData.lexamica_case_id);
    if (exists) {
      console.log('Case already processed, skipping');
      return;
    }
    
    // Store in your database
    await this.saveCase({
      lexamicaId: caseData.lexamica_case_id,
      clientName: `${caseData.client_first_name} ${caseData.client_last_name}`,
      clientEmail: caseData.client_email,
      clientPhone: caseData.client_phone,
      practiceArea: caseData.practice_area,
      incidentDate: caseData.incident_date,
      description: caseData.case_description,
      receivedAt: new Date()
    });
    
    // Send notification
    await this.notifyStaff(caseData);
  }

  // Placeholder methods - implement for your system
  async caseExists(lexamicaId) {
    // Check your database
    return false;
  }

  async saveCase(data) {
    console.log('Saving case:', data);
    // Your database logic
  }

  async notifyStaff(caseData) {
    console.log('Notifying staff about:', caseData.lexamica_case_id);
    // Email/Slack notification
  }
}

// Run the poller
const poller = new CasePoller();

// Poll every 5 minutes
const POLL_INTERVAL = 5 * 60 * 1000;

async function runPoller() {
  console.log('Starting poll...');
  await poller.poll();
  console.log('Poll complete. Next poll in 5 minutes.');
}

// Initial poll
runPoller();

// Schedule subsequent polls
setInterval(runPoller, POLL_INTERVAL);

console.log('Case poller started. Polling every 5 minutes.');
```

### Python Polling Service

```python
import os
import time
from datetime import datetime, timedelta
import requests

class CasePoller:
    def __init__(self):
        self.org_id = os.environ.get('LEXAMICA_ORG_ID')
        self.private_key = os.environ.get('LEXAMICA_PRIVATE_KEY')
        self.base_url = 'https://integration.lexamica.com'
        self.last_poll_time = None
    
    def poll(self):
        now = datetime.utcnow()
        
        # Default to last 24 hours on first run
        from_time = self.last_poll_time or (now - timedelta(days=1))
        
        try:
            events = self.fetch_events(from_time, now)
            
            if events:
                print(f"Found {len(events)} new case(s)")
                self.process_events(events)
            else:
                print("No new cases")
            
            self.last_poll_time = now
            
        except Exception as e:
            print(f"Polling failed: {e}")
    
    def fetch_events(self, from_time, to_time):
        params = {
            'event': 'Case Received',
            'from': from_time.isoformat() + 'Z',
            'to': to_time.isoformat() + 'Z',
            'limit': 100
        }
        
        response = requests.get(
            f"{self.base_url}/organization/{self.org_id}/stored-events",
            params=params,
            headers={'Authorization': f'Bearer {self.private_key}'}
        )
        response.raise_for_status()
        
        return response.json().get('events', [])
    
    def process_events(self, events):
        for event in events:
            self.handle_case_received(event['payload'])
    
    def handle_case_received(self, case_data):
        print(f"Processing case: {case_data.get('lexamica_case_id')}")
        
        # Store in your database
        # save_to_database(case_data)
        
        # Notify staff
        # send_notification(case_data)

if __name__ == '__main__':
    poller = CasePoller()
    poll_interval = 300  # 5 minutes
    
    print(f"Case poller started. Polling every {poll_interval} seconds.")
    
    while True:
        print("Starting poll...")
        poller.poll()
        print(f"Poll complete. Next poll in {poll_interval} seconds.")
        time.sleep(poll_interval)
```

***

## ❓ FAQ

### ❓ "How often should I poll?"

**📝 Full Answer:**

| Use Case               | Recommended Interval |
| ---------------------- | -------------------- |
| Near real-time         | Every 1-5 minutes    |
| Standard business ops  | Every 15-30 minutes  |
| Daily batch processing | Once per day         |

Consider API rate limits and your business needs.

***

### ❓ "How do I avoid processing the same event twice?"

**🔍 Quick Check:**

* Track the `_id` of each processed event
* Or track `lastPollTime` and always query from that point

**📝 Full Answer:** Use a combination of:

1. Track your last successful poll timestamp
2. Store processed event IDs or Lexamica case IDs
3. Check for duplicates before processing

***

### ❓ "How long are events stored?"

**📝 Full Answer:** Events are retained for a configurable period (typically 30-90 days). Always sync to your own database for long-term storage.

***

## 🔧 Troubleshooting

**"No events returned"**

* Check your date range (`from` and `to` parameters)
* Verify the subscription is `active`
* Ensure events have occurred within the time window

**"Authentication failed"**

* Use your **Private Key** for polling (not Public Key)
* Check the key is in the Authorization header

**"Missing events"**

* Ensure your `from` time doesn't have gaps from previous polls
* Consider overlapping your time windows slightly

***

## ➡️ Next Steps

* **Need real-time notifications?** See [Receive Cases via Webhook](/example-integrations/2a.-receive-cases-webhook.md)
* **Want to accept/decline via API?** See [Handler Firm: Full Integration with Polling](/example-integrations/4b.-handler-firm-polling.md)
* **Need to receive files?** See [File Operations](/example-integrations/apx-1.-file-operations.md)

***

*Last updated: January 2026*
