---
name: "maison-webhooks"
description: "Manage a hotel's incoming webhooks: named inboxes its other systems (survey tools, CRMs, form builders, automation platforms) POST to, which start the agents that list them as inputs. Create, read, update and delete webhooks; add, rename, reveal, rotate and revoke their named keys; read the request log and delivery analytics. Use when wiring an external system to a hotel's agents or diagnosing why a sender's requests are refused. Tools: add_webhook_key, create_webhook, delete_webhook, get_webhook, get_webhook_analytics, get_webhook_request, list_webhook_requests, list_webhooks, rename_webhook_key, reveal_webhook_key, revoke_webhook_key, rotate_webhook_key, update_webhook."
license: "Proprietary. Use with a Maison API key under the Maison terms of service."
metadata:
  author: "maison-labs"
  version: "1.0.0"
  category: "webhooks"
  api-base: "https://maison-labs.com"
  openapi: "https://maison-labs.com/api/v1/openapi.json"
  docs: "https://docs.maison-labs.com/skills/maison-webhooks/"
---

# Maison — Incoming webhooks

Manage a hotel's incoming webhooks: named inboxes its other systems (survey tools, CRMs, form builders, automation platforms) POST to, which start the agents that list them as inputs. Create, read, update and delete webhooks; add, rename, reveal, rotate and revoke their named keys; read the request log and delivery analytics. Use when wiring an external system to a hotel's agents or diagnosing why a sender's requests are refused.

Part of the Maison public API. Read the `maison-public-api` skill first for authentication, discovery and transport details (`https://docs.maison-labs.com/skills/maison-public-api/SKILL.md`).

## When to use

- Give an external system an address to send events to — `create_webhook`, then list it in the agent's `scope.webhookIds`.
- Issue a separate key per sending app, or replace a leaked one — `add_webhook_key`, `rotate_webhook_key`, `revoke_webhook_key`.
- Find out why a sender's requests fail or what it sent — `list_webhook_requests`, `get_webhook_request`, `get_webhook_analytics`.

## Before you start

1. Send `x-api-key: <key>` on every call; the key only reaches `/api/v1`.
2. `GET /api/v1/tools` (or MCP `tools/list`) returns the tools THIS key may run — if a tool below is missing there, you lack a grant; do not retry it.
3. Client-scoped tools take `clientId` (a hotel id). `whoami` lists the ids you can act on.

## Tools

| Tool | What it does | REST | Needs |
| --- | --- | --- | --- |
| `add_webhook_key` | Add a named key to an `api_key` webhook — one per sending app, so the request log shows which app called (at most 20 live). | `POST /api/v1/client/{clientId}/webhook/{webhookId}/key` | update-client WRITE (client) |
| `create_webhook` | Create an incoming webhook: a permanent address (`/webhooks/{clientId}/{slug}`) other systems POST to. | `POST /api/v1/client/{clientId}/webhook` | update-client WRITE (client) |
| `delete_webhook` | Soft-delete a webhook: it stops accepting requests at once, its request log stays readable and its address stays reserved (it cannot be reused). | `DELETE /api/v1/client/{clientId}/webhook/{webhookId}` | update-client WRITE (client); destructive |
| `get_webhook` | Fetch one webhook (deleted ones included) with its address, settings, live keys (prefixes only — never a secret) and 24-hour request count. | `GET /api/v1/client/{clientId}/webhook/{webhookId}` | update-client READ (client); read-only |
| `get_webhook_analytics` | Request counts for a hotel's webhooks over `from`–`to` (inclusive UTC days, at most 92): per day (total, accepted, rejected), per webhook (with the last request time) and per outcome. | `GET /api/v1/client/{clientId}/webhooks/analytics` | update-client READ (client); read-only |
| `get_webhook_request` | One logged request with the caller's IP, its stored headers and body (bodies are kept 90 days; after that `body` is null). | `GET /api/v1/client/{clientId}/webhook/{webhookId}/request/{requestId}` | update-client READ (client); read-only |
| `list_webhook_requests` | A webhook's request log, newest first: when, status, outcome (`accepted` or why it was refused), size, the key that authenticated it and how many agent runs it started. | `GET /api/v1/client/{clientId}/webhook/{webhookId}/requests` | update-client READ (client); read-only |
| `list_webhooks` | List a hotel's incoming webhooks with their addresses, live keys (prefixes only, never secrets) and 24-hour request counts, plus how many are deleted (`deletedCount`). | `GET /api/v1/client/{clientId}/webhooks` | update-client READ (client); read-only |
| `rename_webhook_key` | Rename a live key with its current `version`; the secret does not change. | `PUT /api/v1/client/{clientId}/webhook/{webhookId}/key/{keyId}` | update-client WRITE (client) |
| `reveal_webhook_key` | Return a live key's secret (`apiKey`) again. | `GET /api/v1/client/{clientId}/webhook/{webhookId}/key/{keyId}/secret` | update-client WRITE (client) |
| `revoke_webhook_key` | Revoke a key at once (soft: the request log keeps its name). | `DELETE /api/v1/client/{clientId}/webhook/{webhookId}/key/{keyId}` | update-client WRITE (client); destructive |
| `rotate_webhook_key` | Issue a new secret for a key, with the key's current `version`. | `POST /api/v1/client/{clientId}/webhook/{webhookId}/key/{keyId}/rotate` | update-client WRITE (client) |
| `update_webhook` | Edit a webhook's name, description, enabled state, access mode or key rotation, with its current `version`. | `PUT /api/v1/client/{clientId}/webhook/{webhookId}` | update-client WRITE (client) |

Full input fields for every tool: [references/tools.md](references/tools.md).

## Recommended workflows

### Connect a sending system

1. `create_webhook` with a name, a `slug` (the permanent address segment) and an access mode (`api_key` by default). The response carries the webhook's address and its first key, `Default` — the key secret is returned once, so hand it to the sender straight away.
2. Configure the sender to POST to the address with the key in `X-Maison-Key` (or `Authorization: Bearer`).
3. `add_webhook_key` for each further sending app, so the request log shows which app called.
4. Point an agent at it: `update_agent` with the webhook id in `scope.webhookIds`.
5. `list_webhook_requests` to confirm the first request arrived and was accepted.

### Rotate or recover a key

1. `reveal_webhook_key` returns a live key's secret again (the reveal is recorded in the hotel's activity log; it rotates the key first when automatic rotation is due).
2. `rotate_webhook_key` with the key's `version` issues a new secret; the previous one keeps working for 24 hours so the sender can switch.
3. `revoke_webhook_key` stops a key at once.

## Tips

- Automatic rotation is lazy: a due key rotates on its next inbound request or reveal, never on a schedule.
- Request bodies are kept for 90 days; the log row stays after that with `body: null`.
- A webhook does nothing on its own — it starts work only when an enabled agent lists it as an input.

## Pitfalls

- The address (`/webhooks/{clientId}/{slug}`) never changes; to get a new one, create a new webhook.
- The last live key of an `api_key` webhook cannot be revoked (409 `last_key`); switch the webhook to `open` access instead.
- Switching a webhook to `open` revokes every live key; switching back creates a new `Default` key, returned once.
- Never log or echo a key secret back to the user beyond the one hand-off they asked for.
- Revealing, rotating and adding keys need WRITE on `update-client`; a READ-only key can list webhooks and the request log but never sees a secret.

## Calling conventions

Same tool, three transports — pick whichever your runtime already speaks. Always send `x-api-key`.

```bash
# REST
curl -X POST "https://maison-labs.com/api/v1/client/<clientId>/webhook/<webhookId>/key" -H "x-api-key: $MAISON_API_KEY" -H "content-type: application/json" -d '{ "name": … }'
```

```json
// MCP (https://maison-labs.com/mcp, Streamable HTTP): tools/call
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "add_webhook_key", "arguments": { "name": … } } }
```

```json
// A2A (https://maison-labs.com/a2a): tasks/send with a data part
{ "jsonrpc": "2.0", "id": 1, "method": "tasks/send", "params": { "message": { "role": "user", "parts": [{ "type": "data", "data": { "tool": "add_webhook_key", "input": { "name": … } } }] } } }
```

Errors are the same everywhere: `401` no/invalid key; `403 forbidden` with `details.missing` (the grants you lack — fall back to the composite's parts or stop); `403 not_entitled` (the hotel's plan lacks the feature); `404`, `409`, `413 too_large`, `503`. Inputs that fail validation return `400 invalid_input` with the Zod issues — fix and retry once.
