Best Practices
Follow these patterns to build a reliable, production-ready webhook integration with Infodeck.
Return 200 Quickly
Your endpoint must return a 200 status code within 30 seconds. If processing takes longer, acknowledge receipt immediately and handle the event asynchronously.
Good -- acknowledge and queue:
app.post('/webhooks', express.raw({ type: 'application/json', limit: '1mb' }), (req, res) => {
// Verify signature first
// See Webhook Security guide for verifyWebhookSignature() implementation
if (!verifyWebhookSignature(req.body.toString(), req.headers['x-infodeck-signature'], SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(req.body);
// Acknowledge immediately
res.status(200).json({ received: true });
// Process asynchronously (queue, background job, etc.)
processEventAsync(event).catch(err => {
console.error(`Failed to process event ${event.id}:`, err);
});
});
Bad -- blocking processing before responding:
// DO NOT do this
app.post('/webhooks', async (req, res) => {
const event = JSON.parse(req.body);
await updateDatabase(event); // 2 seconds
await notifySlack(event); // 3 seconds
await syncExternalSystem(event); // 10 seconds
res.status(200).json({ received: true }); // Too slow!
});
If your endpoint does not respond within 30 seconds, Infodeck treats the delivery as failed and schedules a retry. Slow endpoints will accumulate retries and eventually be marked as "Failing".
Handle Duplicates with Idempotency
Infodeck guarantees at-least-once delivery. In rare cases (network issues, retries), your endpoint may receive the same event more than once. Use the event.id field to deduplicate.
The following in-memory example illustrates the concept. Do not use an in-memory Set in production -- it resets on restart and does not work across multiple server instances. See the database example below for a production-grade approach.
const processedEvents = new Set();
async function handleEvent(event) {
// Check if already processed
if (processedEvents.has(event.id)) {
console.log(`Skipping duplicate event: ${event.id}`);
return;
}
// Process the event
await doWork(event);
// Mark as processed
processedEvents.add(event.id);
}
Production-grade approach using a database:
async function handleEvent(event) {
try {
// Use a conditional write to atomically check-and-insert
await db.put({
TableName: 'ProcessedWebhookEvents',
Item: {
eventId: event.id,
processedAt: Date.now(),
ttl: Math.floor(Date.now() / 1000) + 7 * 86400 // 7-day TTL
},
ConditionExpression: 'attribute_not_exists(eventId)'
});
} catch (err) {
if (err.name === 'ConditionalCheckFailedException') {
console.log(`Duplicate event: ${event.id}`);
return; // Already processed
}
throw err;
}
await doWork(event);
}
Retry Behavior
When a delivery fails (your endpoint returns 4xx/5xx or times out), Infodeck retries with exponential backoff over approximately 24 hours. Retry intervals increase from minutes to hours between attempts.
After several consecutive failures for a single delivery, that delivery is marked as failed.
If a webhook endpoint accumulates multiple failed deliveries, its status changes to Failing. You will see this in Settings > Webhooks and receive an email notification.
A webhook in "Failing" status continues to receive deliveries. Infodeck does not automatically disable webhooks. Fix the underlying issue and the status will return to "Active" after successful deliveries.
Event Ordering
Infodeck preserves event ordering within the same entity. For example, if a work order is created and then updated, you will always receive workorder.created before workorder.updated for that specific work order.
However, events across different entities may arrive in any order. Do not assume asset.created for Asset A will arrive before asset.created for Asset B.
Handling out-of-order events:
Use the created timestamp on each event to determine the true order. If you receive an event with an older timestamp than one you have already processed for the same resource, you can safely skip it.
Monitor Delivery Logs
Infodeck keeps delivery logs for 90 days. Check them regularly in Settings > Webhooks > [Your Webhook] > Delivery Logs.
Each log entry shows:
- Event ID and type
- HTTP status code returned by your endpoint
- Response time (ms)
- Request and response headers
- Request body (the event payload)
- Retry count (if applicable)
You can also query delivery logs via the API:
curl https://app.infodeck.io/api/v2/organizations/{orgId}/webhooks/{webhookId}/deliveries \
-H "Authorization: Bearer {token}"
Rotate Secrets Regularly
Rotate your signing secret every 90 days. Use graceful rotation for zero-downtime transitions.
Quick rotation workflow:
- Call the rotate endpoint with
mode: "graceful" - Deploy the new secret to your server
- During the 24-hour overlap, both old and new secrets work
- After 24 hours, the old secret expires automatically
Filter Events at the Source
Only subscribe to the events your integration actually needs. This reduces load on your endpoint and simplifies your handler logic. If one endpoint only serves one site, one contractor stream, or one asset family, add filtering there too.
# Good -- subscribe to specific events
curl -X POST https://app.infodeck.io/api/v2/organizations/{orgId}/webhooks \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhooks",
"events": ["workorder.created", "workorder.completed"],
"filtering": {
"mode": "include",
"criteria": {
"locationId": ["loc_hq_01"]
}
}
}'
You can update your event subscriptions at any time without rotating your secret:
curl -X PATCH https://app.infodeck.io/api/v2/organizations/{orgId}/webhooks/{webhookId} \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"events": ["workorder.created", "workorder.completed", "workorder.status.changed"]
}'
Error Handling Patterns
Structure your handler to isolate failures and provide useful debugging information:
app.post('/webhooks', express.raw({ type: 'application/json', limit: '1mb' }), (req, res) => {
// 1. Verify signature
const sig = req.headers['x-infodeck-signature'];
// See Webhook Security guide for verifyWebhookSignature() implementation
if (!sig || !verifyWebhookSignature(req.body.toString(), sig, process.env.INFODECK_WEBHOOK_SECRET)) {
console.warn('Webhook signature verification failed');
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(req.body);
// 2. Acknowledge receipt
res.status(200).json({ received: true });
// 3. Route and process
handleEvent(event).catch(err => {
console.error({
message: 'Webhook processing failed',
eventId: event.id,
eventType: event.type,
error: err.message,
stack: err.stack
});
// Alert your monitoring system (Datadog, PagerDuty, etc.)
});
});
async function handleEvent(event) {
switch (event.type) {
case 'asset.created':
return handleAssetCreated(event.data.object);
case 'asset.status.changed':
return handleAssetStatusChanged(event.data.object, event.data.previousAttributes);
case 'workorder.completed':
return handleWorkOrderCompleted(event.data.object);
case 'webhook.test':
console.log('Test event received successfully');
return;
default:
console.log(`Unhandled event type: ${event.type}`);
}
}
Always include a default case in your event handler. As Infodeck adds new event types, your endpoint should gracefully ignore events it does not recognize rather than throwing errors.
Integration Patterns
Digital Twin / 3rd-Party Platform Sync
Webhooks replace polling for real-time IoT data. Instead of calling GET /assets/:id every 30 seconds for 500 sensors (181M API calls/month), subscribe to asset.telemetry.updated and receive data pushed to you.
Setup:
- Create a webhook subscribing to
asset.telemetry.updatedandasset.status.changed - Fetch asset type definitions once per
assetTypeId(for units and labels) and cache them - Process incoming telemetry in real-time
// Cache asset type metadata (units, labels) — fetch once, reuse forever
const assetTypeCache = new Map();
async function getAssetTypeUnits(assetTypeId) {
if (assetTypeCache.has(assetTypeId)) return assetTypeCache.get(assetTypeId);
const res = await fetch(
`https://app.infodeck.io/api/v2/organizations/${ORG_ID}/asset-types/${assetTypeId}`,
{ headers: { 'Authorization': `Bearer ${TOKEN}` } }
);
const { data } = await res.json();
// Build key → unit map: { "Temperature": "°C", "CO2": "ppm" }
const units = {};
for (const prop of data.properties || []) {
units[prop.key] = { name: prop.name, unit: prop.unit, type: prop.type };
}
assetTypeCache.set(assetTypeId, units);
return units;
}
// Webhook handler
app.post('/webhooks', express.raw({ type: 'application/json', limit: '1mb' }), async (req, res) => {
// Always verify signature first (see Webhook Security guide)
const sig = req.headers['x-infodeck-signature'];
if (!sig || !verifyWebhookSignature(req.body.toString(), sig, SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
res.status(200).json({ received: true }); // Acknowledge immediately
const event = JSON.parse(req.body);
if (event.type === 'asset.telemetry.updated') {
const { assetTypeId, name, id } = event.data.object;
const telemetry = event.data.object.lastTelemetry;
const units = await getAssetTypeUnits(assetTypeId);
// Now you have values + units for your digital twin
// e.g., { Temperature: 24.5 } + { Temperature: { unit: "°C" } }
updateDigitalTwin(id, telemetry, units);
}
});
Summary Checklist
| Practice | Why |
|---|---|
| Return 200 within 30 seconds | Prevents unnecessary retries |
| Verify HMAC signature | Confirms authenticity |
Deduplicate with event.id | Handles at-least-once delivery |
| Process asynchronously | Keeps response times fast |
| Subscribe to specific events | Reduces noise and load |
| Monitor delivery logs | Catches failures early |
| Rotate secrets every 90 days | Limits exposure window |
| Handle unknown event types | Future-proofs your integration |
| Use HTTPS only | Required by Infodeck |
| Store secrets in env vars | Keeps credentials out of source code |
Related
- Webhooks Overview -- How webhooks work
- Event Catalog -- Full payload reference for every event type
- Webhook Management API -- Manage webhooks via the API, including delivery logs
- Webhook Security -- Signature verification and secret rotation