# Tanium Webhooks

Tanium Connect (Tanium Cloud) can send data to an **HTTP destination**: the rows of the
connection's source (a saved question, an event source, ...) with the columns the author chose,
on a schedule, manually or on an event. The body is JSON by default and can be split into several
requests. Tanium does **not sign** these requests. You authenticate them with the **Header
Authentication** option on the destination: you pick an *Auth Header Name* and a *Secret*, Connect
sends that header on every request, and your endpoint compares it with the value you stored.

## When to Use This Skill

* How do I receive Tanium Connect HTTP destination webhooks?
* How do I authenticate Tanium Connect requests? (Header Authentication, no signature)
* Does Tanium sign webhook payloads? (No: use a shared secret header)
* How do I accept non-POST deliveries (for example PUT or PATCH) from Tanium Connect?
* How do I handle a JSON array of Tanium records?
* Why does my Tanium webhook endpoint return 401?

## Verification (core)

```javascript
const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifyTaniumRequest(
  headers,
  secret = process.env.TANIUM_WEBHOOK_SECRET,
  headerName = process.env.TANIUM_WEBHOOK_HEADER || 'Authorization'
) {
  if (!secret) return false;                        // never accept when unconfigured
  const received = headers[headerName.toLowerCase()];
  return Boolean(received) && safeEqual(received, secret);  // WHOLE value, no prefix parsing
}

```

* The header name is whatever you typed as **Auth Header Name** (default `Authorization` in these
  examples), looked up case-insensitively. The expected value is the exact, whole **Secret**,
  including any `Bearer ` prefix you typed. Do not strip or parse a prefix.
* Return `401` for a missing header or a mismatch, `400` for a body that isn't valid JSON, `200`
  once handled. An empty body is fine.
* Read the body raw (`express.raw({ type: '*/*' })`) so any content type works, authenticate, then
  parse.
* **A static shared credential authenticates the sender, not the body, and gives no replay
  protection** (no signature, no timestamp). Use HTTPS and rotate the secret if it leaks.
* Hookdeck's Tanium source (type `TANIUM`) checks exactly this: API Key Header plus exact whole API
  Key. The destination's **Basic Authentication** and **OAuth2 Client Credentials** options are
  not covered by it; use Header Authentication.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/tanium-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/tanium-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/tanium-webhooks/examples/fastapi/).

## Methods and Payload

* **The HTTP method is chosen on the destination**, so don't make the route POST-only. Tanium's
  docs don't list which methods the drop-down offers (only `POST` is named, in the batching
  notes). The examples accept `POST`, `PUT` and `PATCH` for illustration; add whatever method your
  destination uses. Hookdeck's Tanium source pre-selects `POST` without managing the
  method: if the destination sends anything else, also allow that method on the source.
* **There is no event field.** The body is defined by the connection's configuration: usually an
  array of row objects keyed by the columns the author picked, or one object. Handle both. The
  examples use a clearly labelled illustrative payload, not a Tanium-defined schema:

```json
[
  { "Computer Name": "host-1.example.com", "IP Address": "10.0.0.5", "Operating System": "Windows Server 2022" }
]

```

## Environment Variables

```bash
TANIUM_WEBHOOK_HEADER=Authorization       # the Auth Header Name set on the destination
TANIUM_WEBHOOK_SECRET=your-secret-value   # the exact, whole Secret (including any prefix)

```

## Local Development

Tanium Cloud can't reach `localhost`. Tunnel with the Hookdeck CLI (no account required):

```bash
npx hookdeck-cli listen 3000 tanium --path /webhooks/tanium

```

Use the printed URL as the HTTP destination URL in Tanium Connect.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/tanium-webhooks/references/overview.md) - What Connect HTTP destinations send, methods, payload shape
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/tanium-webhooks/references/setup.md) - Create the HTTP destination with Header Authentication
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/tanium-webhooks/references/verification.md) - Header check details, limits of a static secret, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/tanium-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests