> 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/4b.-handler-firm-polling.md).

# Handler Firm: Full Integration with Polling

Receive case invitations via polling, respond via API, and sync status updates bidirectionally.

***

## 🎯 Overview

> **📌 TL;DR**
>
> You're a firm that receives referrals. Poll for new invitations, accept or decline via API, and send/receive status updates—all without needing webhook infrastructure.

**This guide is for you if:**

* You receive case referrals from other firms via Lexamica
* You want to accept/decline invitations programmatically
* You can't receive webhooks or prefer polling
* You need to send status updates from your system

For real-time webhooks instead, see [Handler Firm: Full Integration with Webhooks](/example-integrations/4a.-handler-firm-webhook.md)

***

## 📖 Key Terms

| Term             | Definition                                      |
| ---------------- | ----------------------------------------------- |
| **Handler**      | Your firm—you receive and handle referred cases |
| **Invitation**   | A request for you to handle a case              |
| **Stored Event** | An event stored for polling retrieval           |

***

## ⚙️ Architecture

```
Handler Firm: Full Integration (Polling)
────────────────────────────────────────

┌─────────────────────────────────────────────────────────────────┐
│                         YOUR SYSTEM                             │
├─────────────────────────────────┬───────────────────────────────┤
│       POLL (Receive)            │         SEND (API Calls)      │
│                                 │                               │
│ ┌─────────────────────────────┐ │ ┌─────────────────────────────┐
│ │   Polling Service           │ │ │   API Client                │
│ │                             │ │ │                             │
│ │ Every N minutes:            │ │ │ Actions:                    │
│ │ • Fetch new invitations     │ │ │ • Accept invitation         │
│ │ • Fetch incoming updates    │ │ │ • Decline invitation        │
│ │                             │ │ │ • Send status updates       │
│ │ Process:                    │ │ │                             │
│ │ • Store new invitations     │ │ │                             │
│ │ • Sync incoming updates     │ │ │                             │
│ └──────────────┬──────────────┘ │ └──────────────┬──────────────┘
│                │                │                │               │
└────────────────┼────────────────┴────────────────┼───────────────┘
                 │ GET /stored-events              │ POST
                 ▼                                 ▼
┌─────────────────────────────────────────────────────────────────┐
│                         LEXAMICA                                │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │              Stored Events Database                      │   │
│  │  • Case Invitation Sent                                  │   │
│  │  • Case Update Made                                      │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│  Invitation Actions ◄────────────────────── Accept/Decline     │
│  Case Updates ◄──────────────────────────── POST /update       │
└─────────────────────────────────────────────────────────────────┘
```

***

## 📋 Prerequisites

**Credentials:**

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

**Infrastructure:**

* [ ] Ability to run scheduled polling 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
* [ ] [Inbound Webhooks](/3.-inbound-webhooks.md) — responding to invitations and sending updates
* [ ] [Event Storage and Polling](/5.-event-storage-polling.md) — polling for events from Lexamica

***

## 💡 Step-by-Step Implementation

### Step 1: Create Mappings

Same mappings as the webhook version:

**CaseInvitation Mapping:**

```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": "Invitation Mapping",
    "modelName": "CaseInvitation",
    "fieldMappings": [
      { "lexamicaField": "_id", "foreignField": "invitation_id", "lexamicaFieldType": "String", "foreignFieldType": "String" },
      { "lexamicaField": "referral._id", "foreignField": "case_id", "lexamicaFieldType": "String", "foreignFieldType": "String" },
      { "lexamicaField": "creator", "foreignField": "originator_firm_id", "lexamicaFieldType": "String", "foreignFieldType": "String" },
      { "lexamicaField": "expires", "foreignField": "expires_at", "lexamicaFieldType": "Date", "foreignFieldType": "String" }
    ]
  }'
```

**CaseUpdate Mapping:**

```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": "Case Update Mapping",
    "modelName": "CaseUpdate",
    "fieldMappings": [
      { "lexamicaField": "referral._id", "foreignField": "case_id", "lexamicaFieldType": "String", "foreignFieldType": "String" },
      { "lexamicaField": "title", "foreignField": "title", "lexamicaFieldType": "String", "foreignFieldType": "String" },
      { "lexamicaField": "content", "foreignField": "content", "lexamicaFieldType": "String", "foreignFieldType": "String" },
      { "lexamicaField": "type", "foreignField": "update_type", "lexamicaFieldType": "String", "foreignFieldType": "String" }
    ]
  }'
```

### Step 2: Create Stored Event Subscriptions

```bash
# New invitations
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 Invitation Sent",
    "mapping": "your_invitation_mapping_id",
    "description": "Store incoming invitations for polling",
    "active": true
  }'

# Incoming updates
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 Update Made",
    "mapping": "your_update_mapping_id",
    "description": "Store incoming updates for polling",
    "active": true
  }'
```

### Step 3: Poll for Events

Query stored events periodically.

### Step 4: Respond to Invitations

Same API calls as the webhook version:

```javascript
// Accept
await axios.post(
  `${BASE_URL}/organization/${ORG_ID}/inbound-webhooks/case-invitation/${MAPPING_ID}/accept`,
  { invitation_id: invitationId },
  { params: { Key: PUBLIC_KEY } }
);

// Decline
await axios.post(
  `${BASE_URL}/organization/${ORG_ID}/inbound-webhooks/case-invitation/${MAPPING_ID}/decline`,
  { invitation_id: invitationId, decline_reason: reason },
  { params: { Key: PUBLIC_KEY } }
);
```

### Step 5: Send Status Updates

```javascript
await axios.post(
  `${BASE_URL}/organization/${ORG_ID}/inbound-webhooks/case/${UPDATE_MAPPING_ID}/update`,
  { case_id: caseId, title: title, content: content, update_type: 'status' },
  { params: { Key: PUBLIC_KEY } }
);
```

***

## 🔧 Complete Code Example

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

const config = {
  orgId: process.env.LEXAMICA_ORG_ID,
  publicKey: process.env.LEXAMICA_PUBLIC_KEY,
  privateKey: process.env.LEXAMICA_PRIVATE_KEY,
  invitationMappingId: process.env.LEXAMICA_INVITATION_MAPPING_ID,
  updateMappingId: process.env.LEXAMICA_UPDATE_MAPPING_ID,
  baseUrl: 'https://integration.lexamica.com'
};

// ============================================
// POLLING SERVICE
// ============================================

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

  async poll() {
    const now = new Date();
    const from = this.lastPollTime || new Date(now.getTime() - 24 * 60 * 60 * 1000);
    
    console.log(`Polling from ${from.toISOString()}`);
    
    try {
      // Poll for new invitations
      const invitations = await this.fetchEvents('Case Invitation Sent', from, now);
      if (invitations.length > 0) {
        console.log(`Found ${invitations.length} new invitation(s)`);
        await this.processInvitations(invitations);
      }
      
      // Poll for incoming updates
      const updates = await this.fetchEvents('Case Update Made', from, now);
      if (updates.length > 0) {
        console.log(`Found ${updates.length} new update(s)`);
        await this.processUpdates(updates);
      }
      
      this.lastPollTime = now;
      
    } catch (error) {
      console.error('Poll failed:', error.message);
    }
  }

  async fetchEvents(eventType, from, to) {
    const params = new URLSearchParams({
      event: eventType,
      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 processInvitations(events) {
    for (const event of events) {
      const payload = event.payload;
      
      // Check if we've already processed this invitation
      const exists = await db.pendingCases.exists(payload.invitation_id);
      if (exists) continue;
      
      // Store the new invitation
      await db.pendingCases.create({
        invitationId: payload.invitation_id,
        caseId: payload.case_id,
        originatorFirmId: payload.originator_firm_id,
        clientName: `${payload.client_first_name} ${payload.client_last_name}`,
        practiceArea: payload.practice_area,
        description: payload.description,
        expiresAt: payload.expires_at,
        status: 'pending',
        receivedAt: new Date()
      });
      
      // Alert intake team
      await this.notifyIntake(payload);
    }
  }

  async processUpdates(events) {
    for (const event of events) {
      const payload = event.payload;
      
      await db.caseUpdates.create({
        caseId: payload.case_id,
        title: payload.title,
        content: payload.content,
        source: 'originator',
        receivedAt: new Date()
      });
    }
  }

  async notifyIntake(invitation) {
    console.log(`New invitation: ${invitation.invitation_id}`);
    // Send email/Slack notification
  }
}

// ============================================
// API CLIENT
// ============================================

class LexamicaClient {
  async acceptInvitation(invitationId) {
    const response = await axios.post(
      `${config.baseUrl}/organization/${config.orgId}/inbound-webhooks/case-invitation/${config.invitationMappingId}/accept`,
      { invitation_id: invitationId },
      { params: { Key: config.publicKey } }
    );
    return response.data;
  }
  
  async declineInvitation(invitationId, reason) {
    const response = await axios.post(
      `${config.baseUrl}/organization/${config.orgId}/inbound-webhooks/case-invitation/${config.invitationMappingId}/decline`,
      { invitation_id: invitationId, decline_reason: reason },
      { params: { Key: config.publicKey } }
    );
    return response.data;
  }
  
  async evaluateInvitation(invitationId) {
    const response = await axios.post(
      `${config.baseUrl}/organization/${config.orgId}/inbound-webhooks/case-invitation/${config.invitationMappingId}/evaluate`,
      { invitation_id: invitationId },
      { params: { Key: config.publicKey } }
    );
    return response.data;
  }
  
  async sendStatusUpdate(caseId, title, content) {
    const response = await axios.post(
      `${config.baseUrl}/organization/${config.orgId}/inbound-webhooks/case/${config.updateMappingId}/update`,
      { case_id: caseId, title, content, update_type: 'status' },
      { params: { Key: config.publicKey } }
    );
    return response.data;
  }
}

// ============================================
// RUN
// ============================================

const poller = new HandlerPoller();
const client = new LexamicaClient();

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

async function run() {
  console.log('Polling...');
  await poller.poll();
  console.log('Done');
}

run();
setInterval(run, POLL_INTERVAL);

// Export client for use in your application
module.exports = { client, poller };

// Example usage in your app:
// await client.acceptInvitation('invitation_id_here');
// await client.sendStatusUpdate('case_id', 'Discovery Complete', 'All depositions done.');
```

***

## 📋 Invitation Actions

Same actions available as the webhook version:

| Action                | When to Use                        |
| --------------------- | ---------------------------------- |
| **Accept**            | You want to handle the case        |
| **Decline**           | You can't take it (provide reason) |
| **Evaluate**          | Need more time to decide           |
| **Contact Attempted** | Tried to reach the client          |
| **Consult Complete**  | Initial consultation done          |

***

## ❓ FAQ

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

**📝 Full Answer:** Invitations have expiration times, so poll frequently enough to respond before they expire:

| Typical Expiration | Recommended Poll Interval |
| ------------------ | ------------------------- |
| 24-48 hours        | Every 5-15 minutes        |
| 1 week             | Every 30-60 minutes       |

***

### ❓ "What if I miss an invitation?"

If an invitation expires before you respond, it's marked as expired. The originator may re-send or try another firm.

***

## ➡️ Next Steps

* **Need real-time notifications?** See [Handler Firm: Full Integration with Webhooks](/example-integrations/4a.-handler-firm-webhook.md)
* **Need to upload files?** See [File Operations](/example-integrations/apx-1.-file-operations.md)

***

*Last updated: January 2026*
