> 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/5.-event-storage-polling.md).

# Event Storage and Polling

How to receive events FROM Lexamica using poll-based retrieval.

***

## Table of Contents

1. [Overview](#-overview)
2. [Key Terms](#-key-terms)
3. [How It Works](#️-how-it-works)
4. [When to Use Polling vs Webhooks](#-when-to-use-polling-vs-webhooks)
5. [Common Use Cases](#-common-use-cases)
6. [FAQ](#-faq)
7. [Technical Reference](#-technical-reference)

***

## 🎯 Overview

> **📌 TL;DR**
>
> If you can't receive webhooks (firewall, batch processing, simpler infrastructure), stored event subscriptions let you poll for events instead. Same events, same mappings—you just pull instead of getting pushed.

Stored event subscriptions provide an alternative to webhook subscriptions. Instead of Lexamica pushing events to your server, events are stored in a database and you retrieve them via API calls on your schedule.

This is ideal when:

* Your system is behind a firewall that blocks inbound connections
* You prefer batch processing over real-time handling
* You want simpler infrastructure without managing webhook endpoints

***

## 📖 Key Terms

| Term                          | Definition                                                                                    |
| ----------------------------- | --------------------------------------------------------------------------------------------- |
| **Stored Event Subscription** | A configuration that tells Lexamica to store a specific event type for you to retrieve later. |
| **Stored Event**              | An individual event record stored in the database, ready for retrieval.                       |
| **Polling**                   | The process of periodically querying the API to retrieve new events.                          |

***

## ⚙️ How It Works

When an event occurs with a stored event subscription active:

```
Event Storage Flow
──────────────────

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│    Lexamica     │────▶│   Subscription  │────▶│    Database     │
│                 │     │    Processor    │     │                 │
│ Event occurs:   │     │                 │     │ Event stored    │
│ "Case Created"  │     │ 1. Find subs    │     │ with mapped     │
│                 │     │ 2. Apply mapping│     │ payload         │
│                 │     │ 3. Store event  │     │                 │
└─────────────────┘     └─────────────────┘     └─────────────────┘

                        ... time passes ...

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│   Your System   │────▶│   Integration   │────▶│    Database     │
│                 │     │      API        │     │                 │
│ GET /stored-    │     │                 │     │ Return matching │
│ events?from=... │     │ Query events    │     │ events          │
│                 │     │                 │     │                 │
└─────────────────┘     └─────────────────┘     └─────────────────┘
        │
        ▼
┌─────────────────┐
│  Process events │
│  in your system │
└─────────────────┘
```

**Key points:**

1. You create a stored event subscription for the events you want
2. When events occur, they're transformed using your mapping and stored
3. You poll the API on your schedule to retrieve events
4. Events include timestamps so you can query by date range

> **🎯 Key Takeaway**
>
> Same events as webhook subscriptions, just a different delivery mechanism. Your polling frequency determines how "real-time" your data is.

***

## 💡 When to Use Polling vs Webhooks

| Factor                | Webhooks                   | Polling                       |
| --------------------- | -------------------------- | ----------------------------- |
| **Latency**           | Real-time (seconds)        | Depends on poll frequency     |
| **Infrastructure**    | Requires public endpoint   | No inbound connections needed |
| **Reliability**       | Retry logic built-in       | You control retry logic       |
| **Complexity**        | Need webhook server        | Just API calls                |
| **Batch processing**  | Handle events individually | Process in batches            |
| **Firewall-friendly** | Requires inbound access    | Outbound only                 |

**Choose Webhooks when:**

* You need real-time notifications
* Your system can expose a public HTTPS endpoint
* You want push-based architecture

**Choose Polling when:**

* You're behind a corporate firewall
* You prefer batch processing (e.g., hourly syncs)
* You want simpler infrastructure
* You're building a desktop or mobile app

> **💡 Tip:** You can use both. Set up webhooks for real-time, and stored events as a backup to catch anything you might have missed.

***

## 📋 Common Use Cases

### 📗 Use Case: Setting Up a Stored Event Subscription

|            |                                                                |
| ---------- | -------------------------------------------------------------- |
| **Goal**   | Start storing events so you can poll for them                  |
| **How**    | Create a stored event subscription for the event type you need |
| **Result** | Future events of this type are stored for retrieval            |

**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 Created",
    "mapping": "64f1a2b3c4d5e6f7a8b9c0d2",
    "description": "Store case creation events for nightly sync",
    "active": true
  }'
```

**Response:**

```json
{
  "_id": "64f1a2b3c4d5e6f7a8b9c0d7",
  "event": "Case Created",
  "active": true,
  "description": "Store case creation events for nightly sync",
  "mapping": {
    "_id": "64f1a2b3c4d5e6f7a8b9c0d2",
    "label": "CRM Case Mapping"
  },
  "created": "2026-01-15T10:30:00.000Z"
}
```

### 📗 Use Case: Polling for Recent Events

|            |                                                    |
| ---------- | -------------------------------------------------- |
| **Goal**   | Retrieve events that occurred since your last poll |
| **How**    | Query the stored events endpoint with a date range |
| **Result** | Array of events with mapped payloads               |

**Request:**

```bash
curl -X GET \
  "https://integration.lexamica.com/organization/{orgId}/stored-events?event=Case%20Created&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 Created",
      "payload": {
        "case_id": "64f1a2b3c4d5e6f7a8b9c0d3",
        "contact_first_name": "Jane",
        "contact_last_name": "Smith",
        "practice_area": "Personal Injury"
      },
      "created": "2026-01-15T10:30:00.000Z"
    },
    {
      "_id": "64f1a2b3c4d5e6f7a8b9c0d9",
      "event": "Case Created",
      "payload": {
        "case_id": "64f1a2b3c4d5e6f7a8b9c0da",
        "contact_first_name": "John",
        "contact_last_name": "Doe",
        "practice_area": "Workers Compensation"
      },
      "created": "2026-01-15T14:22:00.000Z"
    }
  ],
  "total": 2,
  "limit": 100,
  "skip": 0
}
```

### 📗 Use Case: Building a Polling Loop

|            |                                                     |
| ---------- | --------------------------------------------------- |
| **Goal**   | Continuously sync events to your system             |
| **How**    | Run a scheduled job that polls and processes events |
| **Result** | Your system stays in sync with Lexamica             |

**Example polling loop (Node.js):**

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

class LexamicaEventPoller {
  constructor(orgId, apiKey) {
    this.orgId = orgId;
    this.apiKey = apiKey;
    this.baseUrl = 'https://integration.lexamica.com';
    this.lastPollTime = null;
  }

  async pollEvents(eventType) {
    const now = new Date();
    const from = this.lastPollTime || new Date(now - 24 * 60 * 60 * 1000); // Default: last 24 hours
    
    const params = new URLSearchParams({
      event: eventType,
      from: from.toISOString(),
      to: now.toISOString(),
      limit: '100'
    });

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

    this.lastPollTime = now;
    return response.data.events;
  }

  async processEvents(events) {
    for (const event of events) {
      console.log(`Processing ${event.event}: ${event._id}`);
      // Your processing logic here
      await this.syncToCRM(event.payload);
    }
  }

  async syncToCRM(payload) {
    // Implement your CRM sync logic
  }
}

// Run every 5 minutes
const poller = new LexamicaEventPoller('your_org_id', 'your_private_key');

setInterval(async () => {
  try {
    const events = await poller.pollEvents('Case Created');
    if (events.length > 0) {
      await poller.processEvents(events);
      console.log(`Processed ${events.length} events`);
    }
  } catch (error) {
    console.error('Polling failed:', error.message);
  }
}, 5 * 60 * 1000); // 5 minutes
```

### 📗 Use Case: Handling Pagination

|            |                                                         |
| ---------- | ------------------------------------------------------- |
| **Goal**   | Retrieve a large number of events that exceed the limit |
| **How**    | Use `skip` parameter to paginate through results        |
| **Result** | All events retrieved across multiple requests           |

**Paginated retrieval:**

```javascript
async function getAllEvents(orgId, apiKey, eventType, from, to) {
  const allEvents = [];
  let skip = 0;
  const limit = 100;
  
  while (true) {
    const params = new URLSearchParams({
      event: eventType,
      from: from.toISOString(),
      to: to.toISOString(),
      limit: limit.toString(),
      skip: skip.toString()
    });

    const response = await axios.get(
      `https://integration.lexamica.com/organization/${orgId}/stored-events?${params}`,
      { headers: { Authorization: `Bearer ${apiKey}` } }
    );

    const { events, total } = response.data;
    allEvents.push(...events);
    
    if (allEvents.length >= total || events.length < limit) {
      break;
    }
    
    skip += limit;
  }
  
  return allEvents;
}
```

***

## ❓ FAQ

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

**📝 Full Answer:** It depends on your use case:

| Use Case                | Recommended Frequency |
| ----------------------- | --------------------- |
| Near real-time sync     | Every 1-5 minutes     |
| Hourly batch processing | Every hour            |
| Daily reports           | Once per day          |
| Backup to webhooks      | Every 15-30 minutes   |

Consider your system's needs and API rate limits. More frequent polling means more current data but more API calls.

***

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

**📝 Full Answer:** Events are retained for a configurable period (typically 30-90 days). Contact support for your organization's specific retention policy. For long-term storage, sync events to your own database.

***

### ❓ "Can I filter by event type?"

**🔍 Quick Check:**

* ✅ Use the `event` query parameter
* ✅ Pass the exact event name (e.g., `Case Created`)

**📝 Full Answer:** Yes. Add the `event` parameter to filter:

```
GET /stored-events?event=Case%20Created&from=...&to=...
```

If you omit the `event` parameter, you'll get all events from all your stored event subscriptions.

***

### ❓ "What's the difference between stored events and webhook subscriptions?"

**📝 Full Answer:**

| Aspect          | Webhook Subscriptions | Stored Event Subscriptions |
| --------------- | --------------------- | -------------------------- |
| Delivery        | Push (we call you)    | Pull (you call us)         |
| Timing          | Immediate             | On your schedule           |
| Endpoint needed | Yes (public URL)      | No                         |
| Retry handling  | Automatic             | You manage                 |
| Same events     | Yes                   | Yes                        |
| Same mappings   | Yes                   | Yes                        |

Both use the same event types and mapping system—only the delivery mechanism differs.

***

### ❓ "Can I delete events after processing?"

**📝 Full Answer:** Events are automatically deleted after the retention period. If you need to track which events you've processed, maintain a record of event IDs or timestamps in your system rather than relying on deletion.

***

## 🔧 Technical Reference

### Query Parameters

| Parameter | Type     | Required | Description                                  |
| --------- | -------- | -------- | -------------------------------------------- |
| `event`   | String   | No       | Filter by event type (e.g., `Case Created`)  |
| `from`    | ISO 8601 | No       | Start of date range (inclusive)              |
| `to`      | ISO 8601 | No       | End of date range (inclusive)                |
| `limit`   | Number   | No       | Max events to return (default: 50, max: 100) |
| `skip`    | Number   | No       | Number of events to skip (for pagination)    |

### Stored Event Response Format

```json
{
  "_id": "64f1a2b3c4d5e6f7a8b9c0d8",
  "event": "Case Created",
  "organization": "64f1a2b3c4d5e6f7a8b9c0d1",
  "payload": {
    "case_id": "64f1a2b3c4d5e6f7a8b9c0d3",
    "contact_first_name": "Jane",
    "contact_last_name": "Smith"
  },
  "metadata": {
    "originalEventId": "...",
    "subscriptionId": "64f1a2b3c4d5e6f7a8b9c0d7"
  },
  "created": "2026-01-15T10:30:00.000Z"
}
```

### Subscription Fields

| Field         | Type    | Required | Description                          |
| ------------- | ------- | -------- | ------------------------------------ |
| `event`       | String  | Yes      | The event type to store              |
| `mapping`     | String  | Yes      | Mapping ID for transforming payloads |
| `description` | String  | No       | Human-readable description           |
| `active`      | Boolean | No       | Enable/disable (default: true)       |

### Available Events

Stored event subscriptions support the same events as webhook subscriptions:

* **Case Events:** Created, Received, Edited, Fields Updated, Stage Changed, Deleted
* **Invitation Events:** Sent, Accepted, Declined, Cancelled, Expired, Evaluated, Contact Attempted, Consult Complete, Closed
* **Update Events:** Case Update Made
* **Relay Events:** Matched, Rejected, Missed, Stopped
* **Other:** Settlement, File Uploaded, Missing Info

See [Webhook Subscriptions](/4.-webhook-subscriptions.md) for the complete event reference.

> **🔧 Technical Deep-Dive: Efficient Polling Strategy**
>
> For reliable event processing without gaps or duplicates:
>
> ```javascript
> // Track your last successfully processed timestamp
> let lastProcessedTime = loadLastProcessedTime();
>
> async function poll() {
>   const events = await fetchEvents({
>     from: lastProcessedTime,
>     to: new Date()
>   });
>   
>   for (const event of events) {
>     await processEvent(event);
>     // Update after each event in case of failure
>     lastProcessedTime = new Date(event.created);
>     saveLastProcessedTime(lastProcessedTime);
>   }
> }
> ```
>
> This ensures you don't miss events if your process crashes mid-batch.

***

*Last updated: January 2026*
