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

# Webhooks

> Svix-signed event deliveries with write-only custom headers.

# Webhooks

`GET|POST /v0/webhooks` · `GET|PATCH|DELETE /v0/webhooks/:id`
(inbox and project twins, plus `/headers` sub-routes).

```bash theme={null}
curl -X POST $BASE/v0/webhooks -H "authorization: Bearer $KEY" \
  -H 'content-type: application/json' \
  -d '{"url":"https://you.dev/hook",
       "event_types":["email.received","email.sent"],
       "headers":{"x-tenant":"acme"}}'
```

Secrets are `whsec_…`. Every delivery carries `svix-id`, `svix-timestamp`
and `svix-signature` (`v1,<base64 HMAC-SHA256 of "id.timestamp.body">`) —
verify with the Svix library or `verifySignature()` in `@lupin/sdk`
(HMAC hex of the **raw** body — parse after verifying).

Event types: `email.received` (+ `.spam` / `.blocked` / `.unauthenticated`
variants), `email.sent`, `email.delivered`, `email.bounced`,
`email.complained`, `email.rejected`, `email.opened`, `domain.verified`.
Payloads wrap the resource: `{ type: "event", event_type, event_id, message: … }`.

Filter by scope with `inbox_ids` / `project_ids` at create time, or
`add_inbox_ids` / `remove_inbox_ids` (and project equivalents) on update.

## Custom headers

Values are **write-only**: sent with each delivery, never returned.
`GET …/:id/headers` lists names only; `PATCH` rotates atomically:

```json theme={null}
{ "set": { "x-tenant": "new" }, "remove": ["x-old"] }
```

At least one set or remove is required; a header can't appear in both.
