Skip to main content

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 29 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' }), (req, res) => {
// Verify signature first
if (!verifySignature(req)) {
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!
});
danger

If your endpoint does not respond within 29 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.

const processedEvents = new Set(); // In production, use Redis or a database

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:

AttemptDelayCumulative
1st retry1 minute1 minute
2nd retry5 minutes6 minutes
3rd retry30 minutes36 minutes
4th retry2 hours~2.5 hours
5th retry24 hours~26.5 hours

After 5 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.

info

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 an asset is created and then updated, you will always receive asset.created before asset.updated for that specific asset.

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:

  1. Call the rotate endpoint with mode: "graceful"
  2. Deploy the new secret to your server
  3. During the 24-hour overlap, both old and new secrets work
  4. 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.

# 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"]
}'

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' }), (req, res) => {
// 1. Verify signature
const sig = req.headers['x-infodeck-signature'];
if (!sig || !verifyWebhookSignature(req.body.toString(), sig, 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.updated':
return handleAssetUpdated(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}`);
}
}
tip

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.

Summary Checklist

PracticeWhy
Return 200 within 29 secondsPrevents unnecessary retries
Verify HMAC signatureConfirms authenticity
Deduplicate with event.idHandles at-least-once delivery
Process asynchronouslyKeeps response times fast
Subscribe to specific eventsReduces noise and load
Monitor delivery logsCatches failures early
Rotate secrets every 90 daysLimits exposure window
Handle unknown event typesFuture-proofs your integration
Use HTTPS onlyRequired by Infodeck
Store secrets in env varsKeeps credentials out of source code
Was this page helpful?