Webhooks
Every alert, delivered to your own endpoint.
The moment a monitor changes state, CuliPulse sends a signed JSON message to your URL. Open a ticket, post to chat, start a runbook or store it. Your code decides.
A signed JSON message the moment a monitor changes state. Your code decides what happens next.
Included on every plan · no limit on endpoints
{ "version": "1", "event": "monitor.down", "delivery_id": "3f2a9c4e-7b1d-4e8a-9c5f-…", "event_at": 1758700000, "sent_at": 1758700001, "monitor": { "id": "mon_3fk9…", "name": "Checkout API", "type": "http", "target": "https://api.example.com/health" }, "state": "down", "reason": "Connection timed out after 10s", "detail": { "sources_down": ["Singapore", "Frankfurt"], "incident_id": "inc_8c1d…" }, "url": "https://culipulse.dev/monitors/mon_3fk9…"}
{ "event": "monitor.down", "monitor": { "name": "Checkout API" }, "state": "down", "reason": "Connection timed out after 10s"}
Events
Seven events, one simple shape.
Every message has the same envelope: a version, the event name, a delivery id and two timestamps. The rest depends on the event.
Verify it came from us
Check the signature in a few lines.
Each delivery is signed with a secret only you and CuliPulse know. Compute an HMAC-SHA256 of the raw body and compare.
HMAC-SHA256 of the raw body with your signing secret, compared to X-CuliPulse-Signature.
import crypto from 'node:crypto';function verifyCuliPulse(rawBody, signatureHeader, signingSecret) { const expected = 'sha256=' + crypto.createHmac('sha256', signingSecret) .update(rawBody).digest('hex'); const a = Buffer.from(signatureHeader ?? ''); const b = Buffer.from(expected); return a.length === b.length && crypto.timingSafeEqual(a, b);}
import hmac, hashlibdef verify_culipulse(raw_body: bytes, signature_header: str, signing_secret: str) -> bool: expected = "sha256=" + hmac.new( signing_secret.encode(), raw_body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature_header or "")
Delivery
Predictable, and honest when it fails.
Reply with any 2xx within 8 seconds. Do slow work after you respond.
Timeouts and 5xx answers are retried up to 3 times. A 4xx answer is treated as final.
Use the final URL. A redirect counts as a failed delivery.
A webhook only goes live after a test delivery works. If it doesn't, the console tells you why in plain words.


Point it at your endpoint.
Paste a URL, send a test, and you're receiving alerts.
Paste a URL, send a test, done.