# Create a subscription

Source: https://help.ruuster.com/api/webhooks-create

```http
POST https://api.ruuster.com/v1/webhooks
```

Subscribe a URL you host to lead events. Returns 201 and the signing secret, which is shown only once.

- Authentication: `x-api-key` and `x-email` headers of an owner or admin
- Rate limit: 120 requests per minute per key
- Changes only your webhook subscriptions, never your Ruuster data

## Body

JSON, `Content-Type: application/json`, at most 64 KB.

| Name | Type | Required | Description |
|---|---|---|---|
| `url` | string | yes | Public `https://` URL with a valid certificate. No credentials, private, loopback or link-local addresses |
| `events` | string[] | yes | Event kinds, or `lead.*` for every lead kind including ones added later. See [Event catalog](https://help.ruuster.com/api/webhook-events) |
| `description` | string | no | Your label |

## Response

`201 Created` with the subscription object (see [List subscriptions](https://help.ruuster.com/api/webhooks-list)) plus `secret` (`whsec_…`). **Store the secret now**: it isn't returned again. If you lose it, [rotate it](https://help.ruuster.com/api/webhooks-rotate-secret). Use it to [verify every delivery](https://help.ruuster.com/api/webhook-verifying).

Errors: `400 invalid_parameter` (missing or unknown field, unknown event kind), `400 url_not_allowed`, `409 subscription_limit_reached` (10 per brokerage).

## Example request

cURL:

```bash
curl -s -X POST "https://api.ruuster.com/v1/webhooks" \
  -H "x-api-key: $RUUSTER_API_KEY" \
  -H "x-email: $RUUSTER_EMAIL" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://hooks.saguarorealty.com/ruuster","events":["lead.tour_request","lead.contact_agent","lead.favorite","lead.created"],"description":"Hot-lead alerts to our CRM"}'
```

Node.js:

```javascript
const res = await fetch("https://api.ruuster.com/v1/webhooks", {
  method: "POST",
  headers: {
    "x-api-key": process.env.RUUSTER_API_KEY,
    "x-email": process.env.RUUSTER_EMAIL,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "url": "https://hooks.saguarorealty.com/ruuster",
    "events": [
      "lead.tour_request",
      "lead.contact_agent",
      "lead.favorite",
      "lead.created"
    ],
    "description": "Hot-lead alerts to our CRM"
  }),
});
const body = await res.json();
if (!body.ok) throw new Error(`${body.error.code}: ${body.error.message} (${body.requestId})`);
console.log(body.data);
```

Python:

```python
import os
import requests

res = requests.post(
    "https://api.ruuster.com/v1/webhooks",
    headers={"x-api-key": os.environ["RUUSTER_API_KEY"], "x-email": os.environ["RUUSTER_EMAIL"]},
    json={
        "url": "https://hooks.saguarorealty.com/ruuster",
        "events": ["lead.tour_request", "lead.contact_agent", "lead.favorite", "lead.created"],
        "description": "Hot-lead alerts to our CRM",
    },
    timeout=30,
)
body = res.json()
if not body["ok"]:
    raise RuntimeError(f'{body["error"]["code"]}: {body["error"]["message"]} ({body.get("requestId")})')
print(body["data"])
```

## Example response

`201 Created`

```json
{
  "ok": true,
  "data": {
    "id": "whk_2c7f9a4e1b",
    "url": "https://hooks.saguarorealty.com/ruuster",
    "events": ["lead.tour_request", "lead.contact_agent", "lead.favorite", "lead.created"],
    "description": "Hot-lead alerts to our CRM",
    "status": "active",
    "secret": "whsec_5f0b9d2c7e1a48b3a6d4e8f1c0b7a2d9",
    "secretHint": "…a2d9",
    "createdBy": "jane@saguarorealty.com",
    "createdAt": "2026-10-01T14:20:00Z"
  }
}
```
