> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lymo.jp/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive events when work completes

Lymo delivers webhooks via [Svix](https://www.svix.com/). Every delivery carries standard signature headers you can verify with any Svix SDK.

## Before you register: hostname allowlist

Webhook destinations are gated by a **per-organization hostname allowlist**. This is a defense-in-depth check on top of Svix's own URL validation — it prevents a leaked API key from being turned into an SSRF relay via Svix.

<Warning>
  The default allowlist for a new organization is **empty**, which means **every** `POST /v1/webhooks` call fails with `422 invalid_webhook_url` until your organization's allowlist contains the hostname you want to deliver to. Ask your Lymo contact (CSM, admin) to add entries to `organization_settings.webhook_allowed_hostnames` before you start integrating.
</Warning>

Allowlist rules:

* Scheme must be `https://`.
* Exact match: `webhook.site` permits only `webhook.site`.
* Wildcard subdomain: `*.example.com` permits `a.example.com`, `foo.bar.example.com`, but **not** the apex `example.com`.
* Matching is case-insensitive and uses punycode form.

## Register an endpoint

```bash theme={null}
curl -X POST "$LYMO_BASE/v1/webhooks" \
  -H "Authorization: Bearer $LYMO_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://hooks.your-app.example.com/lymo",
    "events": ["video.analysis.completed"]
  }'
```

The response includes a `secret` starting with `whsec_`. **Save it immediately** — it is returned only at creation time and is required for signature verification. There is no rotation endpoint today; if you lose or leak it, delete the endpoint and create a new one.

Omit `events` (or pass `[]`) to subscribe to every event type.

## Event types

| Event                      | Status                      | Fires when                                  |
| -------------------------- | --------------------------- | ------------------------------------------- |
| `video.ready`              | Delivered                   | Video finishes processing and is analyzable |
| `video.analysis.completed` | Delivered                   | `POST /analyses` job succeeds               |
| `video.analysis.failed`    | Delivered                   | `POST /analyses` job fails permanently      |
| `deal.scoring.completed`   | Planned (not yet delivered) | `POST /scorings` job succeeds               |
| `deal.scoring.failed`      | Planned (not yet delivered) | `POST /scorings` job fails permanently      |

The `deal.scoring.*` events are declared in the schema so SDK types remain stable, but the publisher isn't wired up yet — subscribing to them today produces no deliveries. They'll be enabled in a future release.

Full payload schemas are in the **API Reference** tab.

## Signature verification

Every delivery has three headers:

* `svix-id` — unique delivery ID, stable across retries
* `svix-timestamp` — Unix seconds at send time
* `svix-signature` — HMAC signature(s). During secret rotation, multiple space-separated signatures are sent so subscribers have a verification window; any matching one proves authenticity.

Verify with the Svix SDK for your language:

```ts theme={null}
import { Webhook } from "svix";

const wh = new Webhook(process.env.LYMO_WEBHOOK_SECRET!);
const payload = wh.verify(rawBody, {
  "svix-id": req.headers["svix-id"] as string,
  "svix-timestamp": req.headers["svix-timestamp"] as string,
  "svix-signature": req.headers["svix-signature"] as string,
});
```

Reject requests that fail verification. Pass the **raw request body** to `verify` — not a re-serialized JSON object.

## Delivery semantics

* Respond with any `2xx` status to acknowledge; non-`2xx` responses or network failures trigger retries with exponential backoff.
* The `svix-id` header is stable across retries — store received IDs and skip already-processed events to make your handler idempotent.
* Retry schedule, timeout, and retention are handled by Svix. See [Svix delivery documentation](https://docs.svix.com/retries) for specifics.

## List and delete endpoints

```bash theme={null}
# List registered endpoints
curl "$LYMO_BASE/v1/webhooks" -H "Authorization: Bearer $LYMO_KEY"

# Delete an endpoint
curl -X DELETE "$LYMO_BASE/v1/webhooks/ep_2abcXYZ12345" \
  -H "Authorization: Bearer $LYMO_KEY"
```

Endpoint IDs are Svix-issued and begin with `ep_`. Deleting an endpoint is immediate — in-flight deliveries still complete, but no new events are sent.
