> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.fluveo.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.fluveo.com/_mcp/server.

# Webhooks

> **Merchant-public status:** Events GET list/retrieve and WebhookEndpoint
> management are contracted-test. Unsupported Events methods remain unsupported.

Register endpoints with a Fluveo merchant secret key. The server chooses the
merchant and mode before parsing management input. Stored endpoints, delivery,
retries, and automatic disable all use that same owner and mode.

## Manage endpoints

Create and list endpoints at `/v1/webhook_endpoints`. Retrieve, update, and
delete one endpoint with its `we_*` id. Stripe has no secret rotation operation,
so public `/v1` offers none; rotate a secret in the [dashboard](/dashboard/webhooks).

Each endpoint has its own `whsec_*` secret and event filter. The full secret is
returned only on create and rotation. Normal reads show a fixed redacted marker,
update omits the field, and logs do not print the secret.

Unknown, malformed, foreign-owner, and wrong-mode ids all return the same
resource-missing response without repeating the supplied id.

## Verify a signature

Verify the raw request bytes before parsing JSON. Enforce timestamp tolerance,
compare signatures in constant time, and keep real secrets out of source and
logs. SDKs are not supported; the step-by-step check is in
[Verify a webhook signature](/cookbooks/verify-a-webhook).

## Delivery safety

Fluveo checks the endpoint URL at registration and before each delivery. The
sender pins checked addresses, ignores proxies, refuses redirects, and blocks
private or internal destinations. The local-host exception exists only in
separate dev images and is absent from release builds.

Retries keep the original event body, owner, and mode. An event with no proven
owner reaches no endpoint. Repeated failure disables only the failing endpoint.

## Test events

The [dashboard](/dashboard/webhooks) (Test mode only) sends one synthetic event to your
endpoint. Public `/v1` has no test-send operation, as on Stripe. A test event is
signed exactly like a real delivery, with the same headers, and verifies with the
endpoint's own secret under either signature header. Recognize a test event from
its signed body:

* top-level `fluveo_test_event: true`;
* `livemode: false`;
* an `evt_test_` event id, and a `<prefix>_test_` object id such as `pi_test_…`.

A test event is never listed in `/v1/events`, and `GET /v1/events/{id}` answers
`resource_missing` for it. It is sent once and never retried. A repeated send with
the same `Idempotency-Key`, endpoint, and type carries the same event id, so a
receiver that removes duplicates by event id handles it once.

Use the generated API Reference for the exact public methods and schemas.

## Hidden from the public API

Each item below is hidden because it does not follow the Stripe parity ADR (ADR 0004 as amended). The [hidden public `/v1` registry](https://github.com/fluveohq/fluveo/blob/main/tests/api-contract/stripe/hidden-public-v1.json) lists every
hidden item. Public `/v1` answers a hidden operation as an unknown URL, refuses a hidden request
parameter or value with Stripe's `invalid_request_error`, and omits a hidden response key.

* the operation `GET /v1/webhook_endpoints/{endpoint_id}/delivery_attempts` (HIDE0-061)
* the operation `POST /v1/webhook_endpoints/{endpoint_id}/rotate_secret` (HIDE0-071)
* the operation `POST /v1/webhook_endpoints/{endpoint_id}/send_test_event` (HIDE0-072)
* the platform `hs.*` values of `enabled_events` on `POST /v1/webhook_endpoints` (HIDE0-119)
* the platform `hs.*` values of `enabled_events` on `POST /v1/webhook_endpoints/{endpoint_id}` (HIDE0-120)