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

A project webhook fires on the transition, not on every failed check. If a server is down for six hours, you get one 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:

  • Statusmonitor.up, monitor.down, monitor.degraded (some regions failing while others pass).
  • Incidentsincident.created, incident.resolved.
  • Lifecyclemonitor.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:

monitor.down
{
  "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.

verify.js
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?

For new integrations, use project webhooks — clean, edge-triggered events that modern receivers handle well. Use the notification-provider webhook only when you need the repeat-while-down behavior.