Copy const express = require('express');
const crypto = require('crypto');
const axios = require('axios');
const app = express();
app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf; } }));
const config = {
orgId: process.env.LEXAMICA_ORG_ID,
publicKey: process.env.LEXAMICA_PUBLIC_KEY,
webhookSecret: process.env.LEXAMICA_WEBHOOK_SECRET,
invitationMappingId: process.env.LEXAMICA_INVITATION_MAPPING_ID,
updateMappingId: process.env.LEXAMICA_UPDATE_MAPPING_ID,
baseUrl: 'https://integration.lexamica.com'
};
// ============================================
// WEBHOOK HANDLER (Receive)
// ============================================
function verifySignature(rawBody, signature) {
if (!signature) return false;
const expected = 'sha256=' + crypto
.createHmac('sha256', config.webhookSecret)
.update(rawBody)
.digest('hex');
try {
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
} catch { return false; }
}
const handlers = {
'Case Invitation Sent': async (payload) => {
console.log(`New invitation: ${payload.invitation_id}`);
// Store the pending case
await db.pendingCases.create({
invitationId: payload.invitation_id,
caseId: payload.case_id,
clientName: `${payload.client_first_name} ${payload.client_last_name}`,
clientPhone: payload.client_phone,
practiceArea: payload.practice_area,
description: payload.description,
expiresAt: payload.expires_at,
status: 'pending'
});
// Alert intake team
await sendSlackNotification(`New case invitation: ${payload.practice_area}`);
},
'Case Update Made': async (payload) => {
console.log(`Update on case ${payload.case_id}: ${payload.title}`);
// Record the update from originator
await db.caseUpdates.create({
caseId: payload.case_id,
title: payload.title,
content: payload.content,
source: 'originator',
receivedAt: new Date()
});
}
};
app.post('/webhooks/lexamica', async (req, res) => {
const signature = req.headers['x-lexamica-signature'];
const eventType = req.headers['x-lexamica-event'];
if (!verifySignature(req.rawBody, signature)) {
return res.status(401).send('Invalid signature');
}
res.status(200).send('OK');
const handler = handlers[eventType];
if (handler) {
try {
await handler(req.body);
} catch (error) {
console.error(`Error: ${eventType}`, error);
}
}
});
// ============================================
// API CLIENT (Send)
// ============================================
class LexamicaClient {
async acceptInvitation(invitationId) {
return axios.post(
`${config.baseUrl}/organization/${config.orgId}/inbound-webhooks/case-invitation/${config.invitationMappingId}/accept`,
{ invitation_id: invitationId },
{ params: { Key: config.publicKey } }
);
}
async declineInvitation(invitationId, reason) {
return axios.post(
`${config.baseUrl}/organization/${config.orgId}/inbound-webhooks/case-invitation/${config.invitationMappingId}/decline`,
{ invitation_id: invitationId, decline_reason: reason },
{ params: { Key: config.publicKey } }
);
}
async evaluateInvitation(invitationId) {
return axios.post(
`${config.baseUrl}/organization/${config.orgId}/inbound-webhooks/case-invitation/${config.invitationMappingId}/evaluate`,
{ invitation_id: invitationId },
{ params: { Key: config.publicKey } }
);
}
async sendStatusUpdate(caseId, title, content) {
return 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 } }
);
}
}
const lexamica = new LexamicaClient();
// ============================================
// YOUR APPLICATION ENDPOINTS
// ============================================
// Accept invitation from your UI
app.post('/api/invitations/:id/accept', async (req, res) => {
try {
await lexamica.acceptInvitation(req.params.id);
await db.pendingCases.updateStatus(req.params.id, 'accepted');
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Decline invitation from your UI
app.post('/api/invitations/:id/decline', async (req, res) => {
try {
await lexamica.declineInvitation(req.params.id, req.body.reason);
await db.pendingCases.updateStatus(req.params.id, 'declined');
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Send status update from your UI
app.post('/api/cases/:caseId/updates', async (req, res) => {
try {
await lexamica.sendStatusUpdate(req.params.caseId, req.body.title, req.body.content);
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000, () => console.log('Handler firm server running'));