---
search:
  tags:
    - Webhook subscriptions
    - POST
seo:
  description: >-
    Registers a URL to receive event deliveries. The response carries a secret…
    Reference for the POST /api/v1/webhooks endpoint in the Bloomcount Public
    API API.
sidebar:
  label: Subscribe a URL to event deliveries
  badge: POST
title: Subscribe a URL to event deliveries
type: openapi-operation
---
Registers a URL to receive event deliveries. The response carries a `secret` (`whsec_...`) — **store it now**,
it is shown once and never returned again. Losing it means deleting the subscription and creating another.

The URL must be HTTPS, must not carry credentials, and must resolve to a public host: a URL that fails those
checks is refused rather than silently never delivering.

**Verifying deliveries.** Every POST to your URL carries three headers:

- `X-Bloomcount-Signature` — hex HMAC-SHA256 of the raw request body, keyed with your `whsec_` secret.
- `X-Bloomcount-Event` — the delivery type, matching one of the types you subscribed to.
- `X-Bloomcount-Timestamp` — unix seconds at the moment of sending.

Recompute the HMAC over the body exactly as received, before any JSON parsing or re-serialising, and compare
in constant time. Reject anything that doesn't match.

**Retries.** A failed delivery is retried up to 3 times with 5s / 30s / 2min backoff, and each attempt times
out after 10s. Deliveries are at-least-once, so make your handler idempotent — the event ID in the body is the
natural key.

**Errors**

- `422 url_must_be_https` — the URL isn't HTTPS.
- `422 url_invalid` — the URL doesn't parse.
- `422 url_host_not_allowed` — a private, local or otherwise disallowed host.
- `422 url_port_not_allowed` — a non-standard port.
- `422 url_credentials_not_allowed` — the URL embeds a username or password.
- `422 at_least_one_event_required` — `events` is empty.

<Operation source="reference" id="post-api-v1-webhooks" />
