---
title: "Create a webhook endpoint"
method: POST
path: "/api/v1/webhooks"
tags: ["Webhooks"]
---

# Create a webhook endpoint

`POST /api/v1/webhooks`

## Request

- Base URL: `https://propraven.com`
- URL: `https://propraven.com/api/v1/webhooks`
- Auth: HTTP bearer

Creates a new webhook subscription. The returned `secret` is shown ONCE — store it server-side and use it to verify every incoming delivery via the `X-PropRaven-Signature` header (HMAC-SHA256 over `<unix_ms>.<raw_body>`). Reject deliveries where `|now - t| > 5min`. Every request field is validated before anything is created; `filter_value.parcel_ids` entries are stored as canonical ids (`state_fips:county_fips:parcel_id`). Only active endpoints count toward your quota.

## Request body

- WebhookCreate
  - `url` string, uri, required: Customer endpoint. https:// only.
  - `event_types` string[], required: Event types to subscribe to. All three event types are live: create accepts each of them and PropRaven emits each of them.
  - `filter_kind` 'parcel_ids' | 'state_fips' | 'county_fips', required
  - `filter_value` union, required: Shape varies with filter_kind. parcel_ids: explicit list. state_fips: all parcels in a state. county_fips: all parcels in a county within a state.
    - object
      - `parcel_ids` string[], required: Parcels to subscribe to, 1–1000: canonical ids `state_fips:county_fips:parcel_id` (preferred; the `canonical_id` other endpoints return), legacy `SSCCC:parcel_id`, parcel UUIDs, or a bare parcel_id that names exactly ONE served parcel. parcel_id is county-scoped, so a bare id that names several parcels is refused with 422 `parcel_identity_unresolved` listing the candidate canonical ids. Stored and matched on the full identity; deliveries carry `canonical_id`.
    - object
      - `state_fips` string, required: 2-digit state FIPS — subscribe to all parcels in this state.
    - object
      - `state_fips` string, required
      - `county_fips` string, required: 3-digit county FIPS (within the state) — subscribe to all parcels in this county.
  - `description` string, nullable: Optional human-readable label for your dashboard.

## Response `201`

Webhook created. Response includes the one-time secret.

- WebhookCreated: One webhook endpoint, as returned by GET /webhooks (each item) and GET /webhooks/{id}.
  - `id` string, uuid
  - `url` string, uri: Customer endpoint. Must be https://.
  - `secret_prefix` string: First 14 characters of the secret (`whsec_` + 8 hex). Identifies the endpoint; the full secret is shown only in the create response.
  - `event_types` string[]
  - `filter_kind` 'parcel_ids' | 'state_fips' | 'county_fips'
  - `filter_value` union: Shape varies with filter_kind. parcel_ids: explicit list. state_fips: all parcels in a state. county_fips: all parcels in a county within a state.
    - object
      - `parcel_ids` string[], required: Parcels to subscribe to, 1–1000: canonical ids `state_fips:county_fips:parcel_id` (preferred; the `canonical_id` other endpoints return), legacy `SSCCC:parcel_id`, parcel UUIDs, or a bare parcel_id that names exactly ONE served parcel. parcel_id is county-scoped, so a bare id that names several parcels is refused with 422 `parcel_identity_unresolved` listing the candidate canonical ids. Stored and matched on the full identity; deliveries carry `canonical_id`.
    - object
      - `state_fips` string, required: 2-digit state FIPS — subscribe to all parcels in this state.
    - object
      - `state_fips` string, required
      - `county_fips` string, required: 3-digit county FIPS (within the state) — subscribe to all parcels in this county.
  - `description` string, nullable
  - `is_active` boolean: false after DELETE (a soft disable).
  - `created_at` string, date-time
  - `disabled_at` string, date-time, nullable
  - `disabled_reason` string, nullable: `user` when disabled with DELETE; null while active.
  - `deliveries_attempted` integer
  - `deliveries_succeeded` integer
  - `last_delivery_at` string, date-time, nullable
  - `last_success_at` string, date-time, nullable
  - `identity_status` 'canonical' | 'legacy_bare_parcel_ids': parcel_ids endpoints only. `legacy_bare_parcel_ids`: the endpoint was created before 2026-09-22 with bare parcel numbers, and those that do not name exactly one parcel are not matched; recreate it with canonical ids.
  - `secret` string, required: **Shown once.** Copy and store server-side immediately. Used to sign every outgoing delivery.
  - `hint` string, required: Signature-verification reminder.

## Other responses

- `400`: Invalid url, event_types, filter_kind, or filter_value.
- `401`: Missing, malformed or invalid credential.
- `402`: Tier quota reached or feature gated. Body includes upgrade guidance.
- `405`: The endpoint exists but does not support this method. `Allow` lists the methods it does.
- `422`: One or more parcel ids do not name exactly one parcel (`code: parcel_identity_unresolved`). `issues[]` lists each input with `code` (parcel_id_ambiguous | parcel_not_found | invalid_parcel_identifier) and, when ambiguous, the candidate canonical ids. Nothing was created.
- `429`: Rate limit exceeded.
- `500`: Unexpected server error. The body never carries database or driver text.
- `501`: Reserved for an event type the API accepts before PropRaven emits it. No current event type returns 501: All three event types are live: create accepts each of them and PropRaven emits each of them.

---

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