# Kibana Webhooks

The Kibana **Webhook connector** (alerting rule actions) sends an HTTP request to your server
whenever a rule action runs. Requests are **not signed**: there is no signature header and no
timestamp. The connector's authentication setting is `none`, `basic`, `SSL` or `OAuth 2.0`.
Hookdeck's Kibana source verifies **HTTP Basic auth**. Custom headers and OAuth 2.0 are not
covered by Hookdeck's Kibana source. This skill covers Basic auth only (not the Webhook - Case
Management connector).

## When to Use This Skill

* How do I receive Kibana webhooks?
* How do I verify the Kibana Webhook connector's Basic authentication?
* Does Kibana sign webhook payloads? (No: Basic auth only)
* Why does my Kibana webhook endpoint return 401?
* How do I accept PUT, PATCH, GET or DELETE from Kibana, not just POST?
* How do I handle a Kibana rule action's body template?

## Hookdeck Setup

Choose Basic authentication on the Kibana Webhook connector.

Hookdeck's Kibana source pre-selects POST without managing the method, so any other method the
connector uses (PUT, PATCH, GET, DELETE) must also be allowed on the source. Hookdeck never sends
a 401 challenge (no `WWW-Authenticate` round trip), so the credentials must arrive with every
request.

## Verification (core)

Kibana sends `Authorization: Basic base64(username:password)` on every request. Split on the
**first** colon and compare username and password each in constant time, evaluating both:

```javascript
const crypto = require('crypto');
const STRICT_BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;

const digest = (s) => crypto.createHash('sha256').update(s).digest();
const safeEqual = (a, b) => crypto.timingSafeEqual(digest(a), digest(b)); // equal length, never throws

function verifyBasicAuth(header, username, password) {
  if (!username || !password) return false;                  // fail closed
  const m = /^basic\s+(\S+)$/i.exec(header || '');           // scheme is case-insensitive
  if (!m || !STRICT_BASE64.test(m[1])) return false;         // junk after valid base64 fails
  const decoded = Buffer.from(m[1], 'base64').toString('utf8');
  const i = decoded.indexOf(':');
  if (i === -1) return false;
  const userOk = safeEqual(decoded.slice(0, i), username);   // evaluate both, no short-circuit
  const passOk = safeEqual(decoded.slice(i + 1), password);
  return userOk && passOk;
}

```

Return `401` for a missing or malformed header or a mismatch, for **every** method. In Python use
`base64.b64decode(token, validate=True)` and `hmac.compare_digest` on **bytes** (never `str`,
which raises on non-ASCII). Read the body raw (`express.raw({ type: '*/*' })`) so any content
type works, and parse it only after the check.

> **A static shared credential authenticates the sender, not the body, and gives no replay
> protection** (no signature, no timestamp). Use HTTPS, and rotate the credential if it leaks.

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

## Methods and Body

The connector method is `POST` (default), `PUT`, `PATCH`, `GET` or `DELETE` (PATCH, GET and DELETE
are generally available since 9.3). The examples accept all five on `/webhooks/kibana`, check the credential
first, and handle an empty body.

There is no vendor event field. The body is the rule action's mustache template, so its shape is
whatever you configure. The examples use this **illustrative** payload (not a vendor schema):

```json
{"rule": "{{rule.name}}", "status": "{{alert.actionGroup}}", "context": "{{context.message}}"}

```

Parse JSON when it parses, otherwise keep the raw text.

## Environment Variables

```bash
KIBANA_WEBHOOK_USERNAME=kibana                         # user name on the Webhook connector
KIBANA_WEBHOOK_PASSWORD=change-me-to-a-long-random-password

```

## Local Development

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

```

No account required. Use the printed URL as the connector URL (FastAPI: port `8000`).

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/kibana-webhooks/references/overview.md) - Connector behavior, methods, illustrative payload
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/kibana-webhooks/references/setup.md) - Configure the Webhook connector with Basic auth and a rule action
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/kibana-webhooks/references/verification.md) - Basic auth check in Node, Python and Next.js, gotchas
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/kibana-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests