# TATER Ops Event Webhook - reference runner (ADO #1300)

A reference receiver for [TATER Ops Event Webhooks](../../Help/) (Manage > Connections > Ops Event Webhooks). This is a starting point you deploy and customize, not a hosted or shared TATER service.

## Isolation model - read this first

Deploy **one instance of this runner per client tenant**, using that tenant's own:

- Org-scoped TATER API key (Manage > Connections > API Keys)
- Org-scoped Ops Event Webhook subscription secret
- Own Azure subscription / hosting environment

There is no TATER-hosted, multi-tenant version of this runner, and there should not be one you build either. Keeping the receiver inside each client's own tenant means:

- TATER never holds custody of a client's downstream automation credentials.
- A bug, breach, or misconfiguration in one client's automation can't cross into another client's data - there's no shared process, shared queue, or shared credential to pivot through.
- The client controls exactly what their automation can do with their own tickets, because it runs with their own scoped API key, not a cross-org or platform-wide one.

If you're tempted to run this for multiple organizations from one deployment "to save infrastructure," don't - stand up a second, fully separate deployment instead.

## What's in this folder

| Path | What it is |
|---|---|
| `index.js` + `lib.js` | Bare Node.js HTTP server (zero dependencies). `node index.js` and you have a receiver. |
| `__tests__/lib.test.js` | Unit tests for signature verification + the via filter (`npm test`). |
| `.env.example` | Every config value, documented. Copy to `.env` and fill in your org's values. |
| `azure-function/` | The same logic wrapped as an Azure Functions (Node.js, classic v3 model) HTTP-triggered function, if you'd rather deploy to a Function App than run a standalone process. |

Pick whichever hosting model fits how you already run things. Both do the same three things:

1. Verify `X-TATER-Signature` (HMAC-SHA256 over the exact raw request body) in constant time.
2. Apply a `via`-based exclusion filter as defense in depth (on top of, not instead of, the subscription's own "Exclude MCP-created" loop guard on the TATER side).
3. Hand the event to `handleEvent()` - replace the `TODO` there with your real automation.

## Deploy: bare Node.js

```bash
cd ops-event-webhook-runner
cp .env.example .env   # fill in WEBHOOK_SECRET, TATER_API_KEY, TATER_ORG_ID, etc.
npm test                # optional, but do this at least once
node index.js
```

Put a reverse proxy (or your platform's own TLS termination) in front of it so the endpoint you give TATER is HTTPS - TATER's subscription config rejects plain HTTP and private/internal-only addresses.

Run it under a process manager (systemd, pm2, a container orchestrator, whatever you already use) so it survives restarts. `QUEUE_MODE=serial` (the default) only serializes work within this one always-on process - if you scale to multiple replicas behind a load balancer, each replica has its own independent queue, which defeats the purpose. If you need serialization across replicas, put a real queue (Azure Storage Queue, SQS, Redis, etc.) in front of `handleEvent()` instead.

## Deploy: Azure Functions

```bash
cd ops-event-webhook-runner/azure-function
func azure functionapp publish <your-function-app-name>
```

Set these as Function App Application Settings (not in source control):

- `WEBHOOK_SECRET`
- `TATER_API_BASE` (default `https://api.tatersecurity.com/api`)
- `TATER_API_KEY`
- `TATER_ORG_ID`
- `IGNORE_VIA` (default `mcp`)

`function.json` sets `authLevel: "anonymous"` on purpose - authentication here is the HMAC check inside the function, not an Azure Functions key. Anyone can reach the URL; only a request signed with your subscription's secret is accepted.

On a Consumption-plan Function App, more than one instance can run concurrently, so there is no in-memory queue in this version (unlike the bare-Node runner's `QUEUE_MODE=serial`) - each invocation processes its event and awaits the result before responding. If your automation cannot safely run two tickets in parallel, front this function with a Storage Queue instead of relying on in-process state.

## Reliability model - what TATER retries, and what it doesn't

TATER's delivery engine (ADO #1298) retries a delivery when the HTTP response is not 2xx, or the request fails outright (timeout, DNS, connection refused, etc.), with escalating backoff up to 5 attempts before dead-lettering. **It has no visibility into what your automation does after it acks the request.** Both hosting options in this folder always return a 2xx once the signature and JSON parse succeed, regardless of whether your `handleEvent()` logic threw - so:

- A bad signature or malformed body correctly gets a 401/400, and TATER will retry it.
- A bug in *your* automation (the TODO you filled in) does **not** get retried by TATER - you already told TATER the delivery succeeded. Make your own automation idempotent and handle its own failures/retries/alerting; don't rely on TATER's retry loop to paper over a downstream bug.

Design your `handleEvent()` to be safe to run more than once for the same `taskId` - a manual Resend from the Delivery Log, or a network-level retry, can deliver the same event twice.

## Verifying you set it up right

1. In TATER, create/edit the subscription, point its URL at your deployed endpoint, and click **Send Test**. Confirm you see a 2xx in the Delivery Log and a corresponding log line from your runner.
2. Create a real ticket (or use `create_tasker_task` via MCP) and confirm your runner receives `task.created` and, if you left the default "Exclude MCP-created" loop guard on, does **not** receive events for tickets your own runner creates back in TATER.
3. Rotate `WEBHOOK_SECRET` on the TATER side, update your runner's config, and confirm delivery still works - this is your rehearsal for a real secret rotation later.

## See also

- `Docs/Help/` article on Ops Event Webhooks (client-facing setup guide).
- The developer payload/signature spec (ADO #1303) for the exact event schema this runner parses.
