Skip to content

Webhooks & n8n

Webhooks push CondoPulse events into the rest of your tooling the moment they happen: a Slack message when an elevator goes down, a vendor ticket when an incident breaches its SLA, a spreadsheet row per resolved incident. Anything that accepts an HTTP POST can subscribe - and n8n is the natural hub.

/admin → Webhooks (staff) - create, edit, deactivate, and delete hooks. Each webhook has:

FieldNotes
URLWhere events are POSTed
SecretUsed to HMAC-sign every delivery (below)
EventsWhich events this hook receives (subset of the list below)
ActiveInactive hooks are kept but never fired
Last status / last firedDelivery health at a glance - 200, or failed: timeout, with the timestamp of the last attempt

The same CRUD is available over JSON at GET/POST/PATCH/DELETE /api/admin/webhooks[/:id] (staff session or key - see Admin & ops).

EventFired when
incident.createdA new incident is reported (web, mobile, or email-in)
incident.status_changedAn incident moves through its lifecycle (acknowledged, in progress, resolved, verified, reopened)
incident.sla_breachedThe sweep escalates an incident past its acknowledgement target
incident.mergedA duplicate is merged into a target incident
asset.status_changedAn asset’s live status changes (operational / degraded / down / maintenance)

Each event is POSTed as JSON:

{
"event": "incident.sla_breached",
"occurredAt": "2026-06-08T14:02:11.000Z",
"incident": {
"id": 17,
"title": "Elevator A2 stuck on floor 7",
"severity": "critical",
"status": "open",
"slaBreached": true,
"source": "web"
},
"property": { "id": 1, "name": "Harborview Towers" }
}

incident or asset is present depending on the event type; property is always included. Two headers matter:

Content-Type: application/json
x-condopulse-signature: sha256=<HMAC-SHA256 of the raw body, keyed by the webhook secret>

Delivery semantics - design your consumers around these:

  • Fire-and-forget, 5-second timeout. CondoPulse does not retry failed deliveries. The result (200, or failed: timeout, etc.) is recorded as the hook’s last status in the admin tab - check it when debugging.
  • At-most-once, effectively. If your workflow must not miss events, reconcile periodically against GET /api/incidents or the CSV export.
  • Respond fast. Return 200 immediately and process async; a slow receiver gets cut off at 5 s and logged as failed.

Always verify before trusting a payload - the URL is guessable; the signature isn’t. Compute HMAC-SHA256 of the raw request body with the webhook’s secret and compare (timing-safe) to the header:

import crypto from 'node:crypto';
function verify(rawBody, signatureHeader, secret) {
const expected =
'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signatureHeader),
Buffer.from(expected)
);
}

A fuller walkthrough (including an Express receiver and an n8n Function-node version) is in the API reference: Webhooks.

n8n pairs with CondoPulse in both directions: Webhook trigger nodes receive events; Schedule + HTTP Request nodes drive the sweep and digest crons. (The seed data even ships an inactive example hook pointed at http://localhost:5678/webhook/condopulse

  • n8n’s default local address - ready to activate.)

Receiving events:

  1. Add a Webhook node - method POST. Copy its production URL.
  2. In CondoPulse, create a webhook (/admin → Webhooks) with that URL, a strong secret, and the events you want.
  3. First node after the trigger: verify x-condopulse-signature against the raw body using a Code node with the snippet above. Drop anything that fails.
  4. Branch on {{$json.body.event}} with a Switch node and do your thing.

Useful flows to start with:

  • incident.created + severity high|critical → Slack/Telegram message to the manager’s channel, with the incident title and a link to /incidents/[id].
  • incident.sla_breached → create a task in the board’s tracker, assigned to the property manager.
  • asset.status_changed to down on an elevator → SMS the elevator vendor’s dispatch line.
  • incident.status_changed to resolved → wait 48 h, then nudge the reporter (push/email) to use Confirm fixed.
  • One hook per consumer beats one mega-hook fanned out later - per-hook last-status makes failures attributable.
  • Rotate secrets by creating a second hook with the new secret, watching it go green, then deleting the old one - no missed events.
  • Webhook configuration changes are mutations like any other: they’re recorded in the audit log.