| name | maintainx-webhooks-events |
| description | Implement MaintainX webhook handling and event-driven integrations.
Use when setting up webhooks, handling MaintainX events,
or building real-time integrations with MaintainX.
Trigger with phrases like "maintainx webhook", "maintainx events",
"maintainx notifications", "maintainx real-time", "maintainx triggers".
|
| allowed-tools | Read, Write, Edit, Bash(curl:*), Bash(npm:*) |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
MaintainX Webhooks & Events
Overview
Build real-time integrations with MaintainX using webhooks and event-driven patterns for work order updates, asset changes, and maintenance notifications.
Prerequisites
- MaintainX account with webhook access
- HTTPS endpoint accessible from internet
- Understanding of webhook security patterns
MaintainX Event Types
| Event | Description | Use Case |
|---|
workorder.created | New work order created | Notify team, sync to external system |
workorder.updated | Work order modified | Track status changes |
workorder.completed | Work order marked done | Trigger follow-up actions |
asset.updated | Asset information changed | Sync asset data |
request.created | Work request submitted | Auto-create work orders |
Instructions
Step 1: Webhook Endpoint Setup
import express, { Request, Response, NextFunction } from 'express';
import crypto from 'crypto';
const router = express.Router();
const WEBHOOK_SECRET = process.env.MAINTAINX_WEBHOOK_SECRET;
router.use('/webhooks/maintainx', express.raw({ type: 'application/json' }));
function verifySignature(req: Request, res: Response, next: NextFunction) {
const signature = req.headers['x-maintainx-signature'] as string;
const timestamp = req.headers['x-maintainx-timestamp'] as string;
if (!signature || !timestamp || !WEBHOOK_SECRET) {
return res.status(401).json({ : });
}
timestampMs = (timestamp) * ;
fiveMinutesAgo = .() - * * ;
(timestampMs < fiveMinutesAgo) {
res.().({ : });
}
payload = ;
expectedSignature = crypto
.(, )
.(payload)
.();
isValid = crypto.(
.(signature),
.(expectedSignature)
);
(!isValid) {
res.().({ : });
}
req. = .(req..());
();
}
router.(, verifySignature, (req, res) => {
event = req.;
.(, event.);
{
(event);
res.().({ : });
} (error) {
.(, error);
res.().({ : });
}
});
{ router webhookRouter };
Step 2: Event Handler Pattern
interface MaintainXEvent {
id: string;
type: string;
data: any;
createdAt: string;
}
type EventHandler = (event: MaintainXEvent) => Promise<void>;
const eventHandlers: Map<string, EventHandler[]> = new Map();
function on(eventType: string, handler: EventHandler) {
const handlers = eventHandlers.get(eventType) || [];
handlers.push(handler);
eventHandlers.set(eventType, handlers);
}
async function handleMaintainXEvent(event: MaintainXEvent): Promise<void> {
const handlers = eventHandlers.get(event.type) || [];
if (handlers. === ) {
.();
;
}
.(handlers.( (event)));
}
(, (event) => {
.(, event..);
({
: ,
: ,
: [{
: [
{ : , : event.. },
{ : , : event..?. || },
{ : , : event..?. || },
],
}],
});
});
(, (event) => {
{ previousStatus, currentStatus } = event.;
(previousStatus !== currentStatus) {
.();
(event.., previousStatus, currentStatus);
}
});
(, (event) => {
.(, event..);
(event.);
(event..) {
(event.);
}
});
(, (event) => {
.(, event..);
workOrder = (event.);
.(, workOrder.);
});
{ handleMaintainXEvent, on };
Step 3: Idempotency Handling
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
interface IdempotencyResult {
isProcessed: boolean;
result?: any;
}
async function checkIdempotency(eventId: string): Promise<IdempotencyResult> {
const key = `maintainx:webhook:${eventId}`;
const result = await redis.get(key);
if (result) {
return { isProcessed: true, result: JSON.parse(result) };
}
return { isProcessed: false };
}
async function markProcessed(eventId: string, result: any): Promise<void> {
const key = `maintainx:webhook:${eventId}`;
redis.(key, .(result), , * * * );
}
(): <> {
{ isProcessed, result } = (event.);
(isProcessed) {
.();
;
}
handlerResult = (event);
(event., { : (), : handlerResult });
}
Step 4: Webhook Testing Tools
import crypto from 'crypto';
import axios from 'axios';
const WEBHOOK_URL = process.env.WEBHOOK_URL || 'http://localhost:3000/webhooks/maintainx';
const WEBHOOK_SECRET = process.env.MAINTAINX_WEBHOOK_SECRET || 'test-secret';
async function sendTestWebhook(eventType: string, data: any) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const payload = JSON.stringify({
id: `evt_${Date.now()}`,
type: eventType,
data,
createdAt: new Date().toISOString(),
});
const signedPayload = `${timestamp}.${payload}`;
const signature = crypto
.createHmac('sha256', )
.(signedPayload)
.();
{
response = axios.(, payload, {
: {
: ,
: signature,
: timestamp,
},
});
.(, response.);
.(, response.);
} (: ) {
.(, error.?. || error.);
}
}
() {
.();
(, {
: ,
: ,
: ,
: ,
: { : , : },
: { : , : },
});
.();
(, {
: ,
: ,
: ().(),
: { : , : },
});
.();
(, {
: ,
: ,
: ,
: { : , : },
});
}
().(.);
Step 5: Webhook Retry Handler
import Bull from 'bull';
interface WebhookJob {
event: MaintainXEvent;
attempt: number;
maxAttempts: number;
}
const webhookQueue = new Bull<WebhookJob>('maintainx-webhooks', {
redis: process.env.REDIS_URL,
defaultJobOptions: {
attempts: 5,
backoff: {
type: 'exponential',
delay: 1000,
},
},
});
webhookQueue.process(async (job) => {
const { event, attempt, maxAttempts } = job.data;
console.log(`Processing webhook ${event.id} (attempt ${attempt}/${maxAttempts})`);
try {
await handleMaintainXEvent(event);
console.log(`Webhook ${event.id} processed successfully`);
} catch (error) {
console.error(, error);
error;
}
});
(): <> {
webhookQueue.({
event,
: ,
: ,
});
}
webhookQueue.(, {
.(, err);
(job. >= job..!) {
(job.., err);
}
});
Step 6: Webhook Dashboard
import { Router } from 'express';
const router = Router();
router.get('/admin/webhooks', async (req, res) => {
const webhooks = await getRecentWebhooks(50);
res.json({
total: webhooks.length,
webhooks: webhooks.map(w => ({
id: w.id,
type: w.type,
status: w.status,
processedAt: w.processedAt,
error: w.error,
})),
});
});
router.get('/admin/webhooks/:id', async (req, res) => {
const webhook = await getWebhookById(req.params.id);
if (!webhook) {
return res.status(404).json({ error: 'Webhook not found' });
}
res.json(webhook);
});
router.(, (req, res) => {
webhook = (req..);
(!webhook) {
res.().({ : });
}
(webhook.);
res.({ : });
});
{ router webhookAdminRouter };
Output
- Webhook endpoint with signature verification
- Event handler pattern implemented
- Idempotency handling
- Testing tools configured
- Retry queue for reliability
- Admin dashboard for monitoring
Webhook Best Practices
- Always verify signatures - Never process unsigned webhooks
- Respond quickly - Return 200 within 5 seconds, process async
- Implement idempotency - Handle duplicate deliveries
- Use queues - Don't block webhook response on processing
- Monitor failures - Alert on repeated failures
Resources
Next Steps
For performance optimization, see maintainx-performance-tuning.