# Zapier

**Verify, provision, and govern Whisper agent identities from a Zap — no server, no SDK, two tiers.**

## The pain

Your ops team lives in Zapier. A support ticket lands, a Stripe payment clears, a row appears in a sheet — and somewhere downstream you need to know *"is the bot that just called our webhook actually one of ours?"*, or *mint a fresh agent identity* the moment a new customer signs up, without anyone touching a terminal.

Rolling that yourself means a webhook step that shells out to `curl`, hand-rolled JSON for a Cypher-shaped control API, and an API key pasted in plaintext into a "Webhooks by Zapier" action where every collaborator on the Zap can see it. That's the wrong shape for both halves: verification shouldn't need a credential at all, and provisioning shouldn't need you to hand-write a POST body.

The Zapier app collapses both into typed fields — a **Search** step for "look this address up," a **Create** step for "make this happen" — and keeps your key where Zapier keeps secrets: in the connection, never in a Zap's visible steps.

## Two tiers, one connection

The app follows the same Robustness Principle as every other Whisper surface: **liberal in what it accepts.** The connection's API-key field is optional.

- **No key** — the four **Searches** work immediately: verify an IPv6 address is a genuine Whisper agent, pull its RDAP record, walk its certificate-transparency history, see who's been looking it up. Read-only, anonymous, zero setup.
- **With your `whisper_live_...` key** — the same connection *also* unlocks three **Creates**: register a new agent (mints a `/128`, hostname, RDAP record, and its own key), set your tenant's DNS policy, and revoke an agent. Irreversible actions stay gated behind the key; nothing mutates without one.

A blank connection is a *valid* connection — the test step succeeds either way, so a teammate can wire up verification Zaps today and an admin can drop in a key later to unlock provisioning, with no re-plumbing. This is exactly the two-tier design also used by the [n8n](/docs/n8n) and [Make](/docs/make) integrations — pick whichever automation tool your team already runs.

## The Searches (keyless)

Every Search takes one IPv6 address and hits the public identity API at `rdap.whisper.online` — no key, ever:

| Search | Endpoint | Returns |
|---|---|---|
| Verify Agent Identity | `GET /verify-identity?ip={addr}` | `is_whisper_agent`, DANE/JWS verdicts, evidence bundle |
| Lookup RDAP Record | `GET /ip/{addr}` | the RFC 9083 registry object |
| Get Transparency Log | `GET /ip/{addr}/transparency` | certificate-issuance history |
| Get Inbound Lookups | `GET /ip/{addr}/lookups` | who has been resolving this agent |

Two design details matter more than they look:

- **"Not a Whisper agent" is data, not an error.** `verify-identity` answers a negative with HTTP 404 and a full JSON verdict body — the Zapier app treats both 200 and 404 as one successful Search record, so you branch on `is_whisper_agent` in the Zap editor instead of catching an exception. A malformed address (HTTP 400) or an upstream failure (5xx) *does* throw, with the API's own message surfaced verbatim — never a bare "unexpected error."
- **RDAP lookups return nothing, cleanly, on a miss.** A 404 from `/ip/{addr}` becomes an *empty* Search result, which is exactly what Zapier's "only continue if found" and "find or create" patterns expect.

Input is normalized before it ever leaves the Zap: compressed or expanded IPv6, a stray `/128` suffix, surrounding whitespace, an IPv6 zone id — all accepted. Every value that reaches the control plane is escaped through a Cypher-literal escaper (quotes and backslashes doubled), so nothing you paste from a webhook payload can break out of the query.

## The Creates (need your key)

Each Create runs the one control verb, `CALL whisper.agents({op:'…', args:{…}})`, over `POST https://graph.whisper.security/api/query` — the same call the `whisper` CLI and every SDK make, just wrapped in typed Zap fields:

| Create | Op | Fields | What it does |
|---|---|---|---|
| Register Agent | `register` | Name, Contact Email (optional) | Mints a `/128`, `agents.whisper.online` hostname, PTR, RDAP record, and the agent's own API key — **returned once** |
| Set Policy | `policy` | Default Action, Block[], Allow[] | Sets your per-tenant resolver policy: block-list or allow-list mode |
| Revoke Agent | `revoke` | Agent (handle or `/128`) | Fully withdraws the address, PTR, tokens, and key — **irreversible** |

Attempt a Create on a keyless connection and you get a clear, actionable error — *"add your Whisper API key to unlock the control plane"* — never a silent no-op. Set Policy also refuses to run with nothing set, since a Zapier Create must change something rather than merely read; leaving Default, Block, and Allow all empty raises the same kind of explicit error.

## A concrete Zap: provision on signup, verify on webhook

**Zap 1 — "New Stripe customer → register a Whisper agent."**
Trigger: *Stripe — New Customer*. Action: *Whisper — Register Agent*, Name = `{{customer.name}}-bot`, Contact Email = `{{customer.email}}`. Follow with *Google Sheets — Create Row* to log the returned `address`, `fqdn`, and `api_key` (shown once — this is the only place it will ever appear again).

**Zap 2 — "Inbound webhook → verify the caller, branch."**
Trigger: *Webhooks by Zapier — Catch Hook* (your reverse proxy forwards the caller's source `/128`). Action: *Whisper — Verify Agent Identity*, Address = `{{ip}}`. Filter: continue only if `is_whisper_agent` is `true` and `dane_ok` is `true`. This half needs **no key at all** — safe to hand to any teammate who only needs the read side.

## Try it — with stock tools, and with Whisper

**With stock tools** (no Zapier, no Whisper software):

```bash
# same keyless verdict a Zapier Search step returns
curl -s "https://rdap.whisper.online/verify-identity?ip=2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4" \
  | jq '{is_whisper_agent, dane_ok, jws_ok}'

# the RDAP object a Lookup RDAP Record step returns
curl -s https://rdap.whisper.online/ip/2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4 | jq '.status, .events'

# the exact control call a Register Agent Create makes, by hand
curl -sS https://graph.whisper.security/api/query \
  -H 'content-type: application/json' \
  -H 'X-API-Key: whisper_live_...' \
  --data '{"query":"CALL whisper.agents({op:'"'"'register'"'"', args:{label:'"'"'checkout-bot'"'"'}})"}'
```

**With Whisper** (the CLI, for comparison — the Zapier app makes the same calls behind typed fields):

```bash
whisper verify 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4         # same keyless verdict
whisper create --register --name checkout-bot                 # same register call
whisper kill --revoke checkout-bot                             # same revoke call
```

Both paths land on the identical public endpoints and the identical control verb; Zapier just gives each one a typed step and a Zap history instead of a terminal.

## Install it

The app — four searches, three creates, a `lib/` Cypher-literal builder, and a full `zapier validate` test suite — is built entirely on Whisper's public [control API](https://github.com/whisper-sec/whisper-n8n/blob/main/CONTROL_API.md), so every step's request and response is a contract you can read before you trust it. It's currently in Zapier's App Directory review; until it clears, you can drive the same control-plane calls directly against the API — see the [control plane](/docs/control-plane) reference.

Get a key for the control-plane half at [whisper.online/platform](https://whisper.online/platform); the keyless Searches need nothing beyond a running Zap.

## Next

- [n8n](/docs/n8n) — the same two-tier design as a self-hostable workflow node
- [Verify an agent](/docs/verify) — the full seven-proof keyless verification model behind every Search
