---
title: "Create a webhook endpoint (Silver and up)"
method: POST
path: "/webhooks"
---

# Create a webhook endpoint (Silver and up)

`POST /webhooks`

## Request

- Base URL: `https://indexed.vc/api/v1`
- URL: `https://indexed.vc/api/v1/webhooks`
- Auth: API key in header `X-API-Key`
- TypeScript: <https://skmtc.dev/indexed/apis/indexed-vc-api/docs/webhooks/post.ts> (a function with typed inputs; its response is checked against the schema)

Subscribe a URL to funding-round alerts. When a new funding round lands
for a company on your watchlist (`list_id` — one of your saved company
lists), or any company if unscoped, Indexed POSTs a signed JSON event
to your endpoint within ~5 minutes.

**Billing:** 1 credit per successfully delivered event. Test sends and
retries are free; a delivery is never charged twice (idempotent per
delivery id). Deliveries pause if your credits are exhausted and resume
on the retry schedule once topped up.

**Delivery + retries:** 10s timeout, HTTPS only, redirects refused.
Non-2xx responses retry on a backoff of 1m → 10m → 1h → 6h → 24h, then
dead-letter. 20 consecutive failures auto-disable the endpoint
(re-enable via PATCH `is_active: true`).

**Delivery semantics — at-least-once.** Like Stripe/GitHub webhooks, an
event may occasionally be delivered more than once (e.g. your endpoint
200s but the ack is lost, or a worker is reclaimed after a crash).
**Dedupe on `X-Indexed-Delivery-Id`** — process each id once. Billing is
exactly-once regardless: a given delivery is charged at most one credit
even if re-sent (idempotent per delivery id).

**Verifying signatures:** every request carries
`X-Indexed-Signature: t=<unix_seconds>,v1=<hex>` where
`v1 = HMAC_SHA256(secret, "{t}.{raw_body}")`. Recompute over the raw
body, compare constant-time, and reject if `|now - t| > 300s`:
```js
const [t, v1] = header.match(/t=(\d+),v1=([0-9a-f]+)/).slice(1)
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
const valid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))
  && Math.abs(Date.now()/1000 - t) < 300
```
Requests also carry `X-Indexed-Event` and `X-Indexed-Delivery-Id`
(use the delivery id for idempotent processing on your side).

**Event catalog** — `funding_round.created` (new round, announced ≤90 days),
`funding_round.updated` (material correction to an existing round, ≤1/round/day),
`company.created` / `investor.created` (entity entered the visible index, fired once),
and `company.updated` (watchlist-scoped subscriptions only — requires `list_id`;
≤1/company/day). `domain.coverage.added` is opt-in and fires once per
endpoint/domain when a domain previously missed by that user becomes
API-resolvable. `company.*` payloads carry `data.company`, `investor.*`
payloads carry `data.investor`; the funding shape is below.

**Coverage-added payload** (`domain.coverage.added`):
```json
{
  "id": "<delivery uuid>",
  "event": "domain.coverage.added",
  "created_at": "2026-09-01T12:00:00Z",
  "data": {
    "requested_domain": "example.com",
    "company": { "id", "name", "slug", "website", "industries", "hq_city", "hq_country" },
    "domain_match": {
      "queried": "example.com",
      "canonical_domain": "example.com",
      "matched_via": "canonical_domain"
    }
  }
}
```

**Event payload** (`funding_round.created` / `funding_round.updated`):
```json
{
  "id": "<delivery uuid>",
  "event": "funding_round.created",
  "created_at": "2026-07-03T12:00:00Z",
  "data": {
    "company": { "id", "name", "slug", "website", "industries", "hq_city", "hq_country" },
    "funding_round": {
      "event_type": "series_b", "round_name": "Series B",
      "amount_raised_usd": 42000000, "announced_date": "2026-07-01",
      "post_money_valuation": 400000000, "press_url": "https://…",
      "investors": [{ "name": "Example Ventures", "is_lead": true }]
    }
  }
}
```
Endpoint limits: Silver 2, Gold 3, Platinum 10, Enterprise 25.

## Request body

- object
  - `url` string, required: HTTPS endpoint (public host — internal/private addresses rejected)
  - `description` string
  - `event_types` string[]
  - `list_id` string, uuid: Scope to one of your saved company lists (watchlist). Omit for all companies.

## Response `201`

Created. `secret` (whsec_…) is returned ONLY here — store it to verify signatures.

## Other responses

- `400`: Invalid request parameters
- `401`: Missing, invalid, or insufficient API key
- `403`: Invalid request parameters

---

[API](https://skmtc.dev/indexed/apis/indexed-vc-api.md) - [All operations](https://skmtc.dev/indexed/apis/indexed-vc-api/llms.txt) - [OpenAPI document](https://skmtc.dev/indexed/apis/indexed-vc-api/revisions/c111a32d9c0c?raw)
