UnderpassDocs

Manage

Logs and webhooks

The tamper-evident audit log, network logs, webhooks for events, and streaming logs to your own tools.

Audit log

Logs → Audit log records every change, sign-in and refused request in your workspace: who did it, when, from which address, and what changed. Owners and admins can see it.

  • Filters: Machines, Access, Apps & services, Keys, People & sign-ins; by person; by time (24 hours, 7 days, 30 days, 90 days); and search.
  • Export CSV: up to 10,000 events.
  • Kept: 90 days.
  • Tamper-evident: each entry includes a fingerprint (SHA-256) of the one before it, so editing, removing or reordering entries breaks the chain. Click Verify integrity to check it.

Event names look like machine.approved, key.created, policy.updated, member.removed and account.signed_in. When the API refuses a change, it's recorded as <area>.failed.

Network logs

Logs → Network logs shows which machines talked to which, and how much. It's off by default; an owner or admin turns it on.

  • Recorded: for each connection, the protocol, source, destination, packets and bytes each way, packets blocked by access rules, and whether it was between machines, to a subnet, or to the internet. Contents are never recorded.
  • Reported: by the machines themselves, every half minute. Machines on the system WireGuard engine don't report.
  • Kept: 30 days.
  • Filters: 15 minutes to 7 days; machines, subnets or internet; search; auto-refresh; Export CSV.

Network logs need app version 0.5 or newer.

Webhooks

Logs → Webhooks sends events to a URL as they happen.

SettingOptions
FormatJSON, n8n, Slack, Discord, Google Chat
EventsEverything, or any of Machines, Keys, Members, Invites, Access controls, Apps, Services, Workspace settings, Webhooks, Log streams, Sign-ins
URLhttps only; private and internal addresses are refused

A JSON delivery looks like:

json
{
  "id": "…",
  "type": "machine.approved",
  "time": "2026-10-28T10:42:00Z",
  "tailnet": "…",
  "actor": "amara@acme.com",
  "ip": "203.0.113.24",
  "target": { "type": "machine", "id": "…", "name": "db-prod" },
  "details": {},
  "summary": "…"
}

Verify deliveries

JSON and n8n webhooks are signed with a secret (whsec_…), shown once and rotatable. Each request has:

  • X-Webhook-Event: <type>
  • X-Webhook-Signature: t=<unix time>,v1=<hex>, where v1 is the HMAC-SHA256 of "<t>.<raw body>" with your secret

Check the signature, and reject requests older than 5 minutes:

js
import crypto from "node:crypto";

function verify(secret, header, rawBody) {
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
  const a = Buffer.from(v1 ?? ""), b = Buffer.from(expected);
  return fresh && a.length === b.length && crypto.timingSafeEqual(a, b);
}

Send test sends a test.ping event.

Delivery and retries

A failed delivery is retried after 2 seconds, 10 seconds, 1 minute and 5 minutes. After 20 failures in a row the webhook is paused (Paused: failing). The last 50 deliveries are kept, and you can redeliver any of them.

Log streaming

Logs → Log streaming sends your audit log or network logs to a logging service.

DestinationAuthentication
DatadogDD-API-KEY
Splunk HECAuthorization: Splunk <token>
Elastic (bulk API)Authorization: ApiKey <key>
AxiomAuthorization: Bearer <token>
HTTP (JSON)Authorization: Bearer <token>

Events are sent in batches of 100 (500 for network logs), when a batch fills or 5 seconds after its first event. Network log items have type: "network.flow". Failed batches are retried after 2, 10 and 30 seconds.

Limits

Up to 10 webhooks and 10 log streams per workspace.