# Make

**Every Make scenario that touches an agent's network identity is a leap of faith — nobody in the automation can say whether the caller is the agent you provisioned or an impostor replaying its address.**

Verifying that requires a DANE check, a DNSSEC-validated PTR lookup, and an RDAP fetch — three HTTP modules and a pile of `parseJSON` gymnastics most builders skip. Provisioning a new agent normally means shelling out to a control API by hand, because no low-code platform speaks Whisper's control verb natively. The **Whisper Agent Identity** app closes both gaps: four keyless modules that verify an address in one click, and five keyed modules that provision, police, and retire agents inside any scenario.

## The app is two tiers, deliberately

Per Whisper's integration standard, the app never forces a choice between "just a verifier" and "full control." It ships as **one app, two connection tiers**, so a builder gets value immediately and more value the moment they add a key:

| Tier | Connection | Endpoint | Modules |
|---|---|---|---|
| **Keyless** | none (`connection: null`) | `https://rdap.whisper.online` | Verify Agent Identity, Lookup RDAP Record, Get Transparency Log, Get Inbound Lookups |
| **Keyed** | `whisper` (apikey) | `https://graph.whisper.security/api/query` | Register Agent, Set Policy, Get Logs, Revoke Agent, List |

Drop the app into a scenario with no connection at all and the four keyless modules already work — anyone building an "is this caller legit" branch gets it for free. Add a `whisper_live_…` key to the **Whisper API key** connection and the five control modules light up in the same module picker.

> Every module maps a request/response straight onto Whisper's public control API — a plain, readable contract you can check before you trust it. Sibling recipes: [Zapier](/docs/zapier), [Pipedream](/docs/pipedream), and the full wire contract at [`CONTROL_API.md`](https://github.com/whisper-sec/whisper-n8n/blob/main/CONTROL_API.md).

## Mechanism: what "verify" actually checks

**Verify Agent Identity** calls `GET /verify-identity?ip=<address>` and returns a structured verdict, not a guess. Under the hood the endpoint runs the same chain a human would run by hand — the module just does it in one request:

1. **PTR lookup** — reverse the `/128` under `ip6.arpa` and confirm it resolves to the agent's FQDN.
2. **Forward AAAA** — resolve the FQDN back to an address and confirm it matches: forward-confirmed reverse DNS (FCrDNS), which closes the PTR-spoofing loop (IPv6 reverse-DNS per RFC 8501).
3. **DANE/TLSA** — fetch `_443._tcp.<fqdn>` TLSA under DNSSEC and require the **AD** (Authenticated Data) bit, then confirm usage `3 1 1` (DANE-EE, no public CA in the loop — RFC 6698 §2, RFC 4034 for the DNSSEC signature chain).
4. **Served-leaf match** — confirm the certificate actually served on `:443` hashes to that TLSA record.

For the public demo agent at `2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4` (`acef2002a323d40d4.<tenant>.agents.whisper.online`), that chain resolves the TLSA record `3 1 1 b653a4ef…fcb82d1d`. A pass means: this address is a real Whisper agent, its reverse and forward DNS agree, and its certificate is cryptographically pinned by DNSSEC — not merely "some CA signed something."

You *could* run that chain from a terminal — five tools, four round trips, eyeballing the `ad` flag and diffing two hashes by hand (it's on [Verify an agent](/docs/verify)). In Make you don't: one module does all of it.

### The Verify Agent Identity module

Drop **Verify Agent Identity** into a scenario, map `{{1.ip}}` from a webhook trigger to the `ip` field, and the single module returns:

```json
{
  "is_whisper_agent": true,
  "fqdn": "acef2002a323d40d4.<tenant>.agents.whisper.online",
  "dane_ok": true,
  "operator": "<tenant>",
  "verified_at": 1782919008167
}
```

Branch the scenario on `dane_ok = true` and you have a router that trusts nothing it can't cryptographically verify — zero HTTP modules, zero manual DNSSEC reads.

## The keyed half: provisioning as a scenario step

The five keyed modules are thin, deterministic wrappers over the one control verb, `whisper.agents({op, args})` — full contract in `CONTROL_API.md`. Each module builds the Cypher call, POSTs it with the connection's key in `X-API-Key`, and normalizes whichever response envelope the API returns that day (the live procedure-row shape or the flat dev-guide shape) into plain output fields — so a scenario never has to branch on wire format.

**Register Agent**, for example, takes `label` (and optional `contact_email`) and returns a routable identity in one step:

```json
{"query":"CALL whisper.agents({op:'register', args:{label:'checkout-bot'}})"}
```

→ `agent`, `address` (the new `/128`), `fqdn`, `ptr`, and **`api_key`** — the agent's own key, minted once and shown once. A scenario that runs "new customer signs up → provision them an agent → store the key in a data store, never in a log" is five clicks, not a backend deploy.

The other four keyed modules round out the fleet lifecycle: **Set Policy** (`op:'policy'`, `default`/`block[]`/`allow[]` — the tenant's DNS resolver policy), **Get Logs** (`op:'logs'`, windowed `dns`/`conn`/`alloc` activity), **Revoke Agent** (`op:'revoke'`, irreversible), and **List** (`op:'list'`, the tenant's agents/identities/records).

### With stock tools

```bash
curl -s https://graph.whisper.security/api/query \
  -H "X-API-Key: whisper_live_xxxxxxxxxxxxxxxxxxxx" \
  -H 'content-type: application/json' \
  -d '{"query":"CALL whisper.agents({op:'\''register'\'', args:{label:'\''checkout-bot'\''}})"}' \
  | jq .
```

You own the quoting (a Cypher string literal, single quotes doubled), the envelope-shape branch (`result` vs `rows[0].result`), and never accidentally log `api_key`.

### With Whisper (the Make module, and the CLI for comparison)

In Make: **Register Agent** → map `label` from an incoming field → wire `{{5.api_key}}` straight into a data-store "Add a record" module, never a log or a router field that gets echoed downstream. Equivalent from a terminal, for parity:

```bash
whisper agents register --label checkout-bot --json
```

## A worked scenario: gate a webhook on identity, then provision on demand

A realistic flow that uses both tiers in one scenario:

1. **Webhook** — receives `{caller_ip, order_id}` from an upstream service.
2. **Verify Agent Identity** (keyless) — `ip = {{1.caller_ip}}`.
3. **Router** — branch on `{{2.dane_ok}} = true`.
   - *Not verified* → **Slack: Post Message**, "rejected unverified caller `{{1.caller_ip}}`", scenario ends.
   - *Verified* → continue.
4. **List** (keyed, `kind: agents`) — confirm `{{2.fqdn}}`'s agent is still `active` (catches a revoked-but-not-yet-expired DNS cache).
5. **Get Logs** (keyed, `kind: conn`, `from: -1h`) — pull the agent's last hour of connection activity for an audit trail alongside the order.
6. **Data store: Add a record** — order id + verified identity + log excerpt, no bearer/API key ever touched a field that Make persists in the execution history.

Because step 2 needs no connection at all, a builder can prototype steps 1–3 with zero Whisper account, then add the key only when they reach step 4 — exactly the "verify free, provision with a key" shape the app is built to teach.

> **404 is not an error here.** `verifyIdentity` and `lookupRdap` both answer **HTTP 404** for "not a Whisper agent" / "no identity" (a legitimate negative, not a broken endpoint) — Make treats all 4xx as module errors, so the app maps that 404 into a specific, readable `DataError` message. Attach an **error handler** (or set the module to *Resume*) on those two modules so "not an agent" is a normal branch, not a scenario crash.

## Install it

The app is not yet in the public Make app directory pending review; until then, load the sections into **Development → Custom apps** in that order (Base, four keyless modules, one `apikey` connection, five keyed modules), or drive the same layout through Make's [SDK Apps API](https://developers.make.com/api-documentation/api-reference/sdk-apps) if you prefer to script it.

---

**Next:** [Zapier](/docs/zapier) for the equivalent two-tier app on Zapier, or [Verify](/docs/verify) for the DANE/DNSSEC chain this app's keyless modules are built on.
