---
title: "Register a webhook subscription"
method: POST
path: "/v1/webhooks"
tags: ["Webhooks"]
---

# Register a webhook subscription

`POST /v1/webhooks`

## Request

- Base URL: `https://crowdintel.xyz/api`
- URL: `https://crowdintel.xyz/api/v1/webhooks`
- Auth: HTTP bearer

**Min tier: `free`.** Registers an HTTPS callback that the dispatcher POSTs on every new whale/insider signal matching the filters, signed with an `X-CrowdIntel-Signature` HMAC header and retried on failure.

The `secret` is returned **once**, in this response only — `GET /v1/webhooks` always reports `secret: null`. There is no `PATCH`: changing a subscription means delete + recreate, which rotates the secret.

Validation (all rejected with `400 bad_request`):
- `callback_url` must be an absolute `https:` URL with a publicly routable host — `localhost`, `*.local`, `*.internal`, `0.0.0.0` and private/loopback/link-local IP literals are refused (SSRF guard). The host is re-resolved and re-checked at delivery time, so a DNS-rebinding target registers but never fires.
- Filter fields are top-level, not nested.
- Exceeding the cap of 10 subscriptions per account returns `400` (not `409`/`429`).

Unrecognised body fields are ignored.

## Request body

- WebhookCreateRequest: Filter fields are top-level, not nested. Unrecognised fields are ignored.
  - `callback_url` string, uri, required: Absolute https URL with a publicly routable host.
  - `min_score` number: Only deliver signals scoring at or above this. Omit for no floor.
  - `score_type` 'whale' | 'insider': Omit to receive both kinds.
  - `category` string: Market category, compared case-insensitively. Omit for any market.

## Response `201`

Subscription created. `secret` is shown here and never again.

- WebhookCreated
  - `id` integer, required
  - `callback_url` string, uri, required
  - `filters` WebhookFilters, required: Stored filters. An empty object matches every signal.
    - `min_score` number
    - `score_type` 'whale' | 'insider'
    - `category` string
  - `active` boolean, required
  - `secret` string, required: HMAC signing key (`whsec_…`) for the `X-CrowdIntel-Signature` header. Returned ONCE — store it now.
  - `created_at` string, date-time, required

## Other responses

- `400`: Malformed parameters or a rejected guardrail (e.g. unfiltered trades query, cluster blowup, window > 90 days).
- `401`: Missing, invalid, or revoked API key. Returned before the rate limiter runs, so it carries no `X-RateLimit-*` headers.
- `403`: Key valid but its tier is below this endpoint's minimum. Returned before the rate limiter runs, so it carries no `X-RateLimit-*` headers.
- `429`: Hourly or monthly rate limit exceeded.

---

[API](https://skmtc.dev/crowdintel/apis/crowdintel-api.md) · [All operations](https://skmtc.dev/crowdintel/apis/crowdintel-api/llms.txt) · [OpenAPI document](https://skmtc.dev/crowdintel/apis/crowdintel-api/revisions/7059cfe9320d?raw)
