Webhooks
Webhooks push status changes to your own endpoints — a Slack relay, PagerDuty, a Lambda, whatever you run. TunnelHQ sends a clean event when a monitor goes down and another when it recovers.
On this page
Project webhooks
Set these up under Developers → Webhooks. They're edge-triggered: you get exactly one monitor.down when a monitor goes from up to down, and exactly one monitor.up when it recovers.
They don't repeat during an outage
monitor.down at the start and one monitor.up at recovery — never a stream of "still down" calls. That's exactly what HTTP receivers like Slack, Discord, and PagerDuty expect.Events
Subscribe each endpoint to the events it cares about:
- Status —
monitor.up,monitor.down,monitor.degraded(some regions failing while others pass). - Incidents —
incident.created,incident.resolved. - Lifecycle —
monitor.created,monitor.updated,monitor.deleted,monitor.paused,monitor.resumed.
Payload
Every delivery is a JSON POST with the same fixed envelope — event, timestamp, projectId, and an event-specific data object:
{
"event": "monitor.down",
"timestamp": "2026-06-14T08:05:10.000Z",
"projectId": 42,
"data": {
"monitor_id": "srv_916",
"monitor_name": "Sydney-I",
"protocol": "openconnect",
"host": "95.111.222.15",
"port": 443,
"time": "2026-06-14T08:05:10.000Z",
"latency_ms": null,
"message": "Tunnel unreachable"
}
}Verifying deliveries
Give an endpoint a secret and every delivery is signed: the X-TunnelHQ-Signature header carries sha256=<HMAC-SHA256 of the raw body>. Recompute the HMAC with your secret and compare before trusting the payload. Deliveries also include X-TunnelHQ-Event and X-TunnelHQ-Endpoint-Id headers for routing.
const crypto = require("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));
}Delivery log
Every outbound call is recorded with its timestamp and response, so you can see exactly what was sent and when. If you ever wonder whether a webhook fired, the delivery log is the source of truth — not the dashboard's "still down" heartbeat rows, which are status updates, not webhook calls.
Notification webhooks (legacy)
There's a second, older path under Settings → Notifications → Add Webhook, inherited from the monitoring engine TunnelHQ is built on. It also fires on the transition, but it has an optional resend interval — set it above zero and it re-sends every Nth failed check during an outage. It's off by default, which matches the project-webhook behavior above. Reach for it only if you specifically want repeat reminders while something is down.
Which should I use?