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.
Managing webhooks
Section titled “Managing webhooks”/admin → Webhooks (staff) - create, edit, deactivate, and delete hooks.
Each webhook has:
| Field | Notes |
|---|---|
| URL | Where events are POSTed |
| Secret | Used to HMAC-sign every delivery (below) |
| Events | Which events this hook receives (subset of the list below) |
| Active | Inactive hooks are kept but never fired |
| Last status / last fired | Delivery 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).
Events
Section titled “Events”| Event | Fired when |
|---|---|
incident.created | A new incident is reported (web, mobile, or email-in) |
incident.status_changed | An incident moves through its lifecycle (acknowledged, in progress, resolved, verified, reopened) |
incident.sla_breached | The sweep escalates an incident past its acknowledgement target |
incident.merged | A duplicate is merged into a target incident |
asset.status_changed | An asset’s live status changes (operational / degraded / down / maintenance) |
Delivery
Section titled “Delivery”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/jsonx-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, orfailed: 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/incidentsor the CSV export. - Respond fast. Return
200immediately and process async; a slow receiver gets cut off at 5 s and logged as failed.
Verifying the signature
Section titled “Verifying the signature”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 recipes
Section titled “n8n recipes”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:
- Add a Webhook node - method
POST. Copy its production URL. - In CondoPulse, create a webhook (
/admin→ Webhooks) with that URL, a strong secret, and the events you want. - First node after the trigger: verify
x-condopulse-signatureagainst the raw body using a Code node with the snippet above. Drop anything that fails. - Branch on
{{$json.body.event}}with a Switch node and do your thing.
Useful flows to start with:
incident.created+ severityhigh|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_changedtodownon an elevator → SMS the elevator vendor’s dispatch line.incident.status_changedtoresolved→ wait 48 h, then nudge the reporter (push/email) to use Confirm fixed.
Operational notes
Section titled “Operational notes”- 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.