---
title: "Register a webhook endpoint"
method: POST
path: "/v1/webhook-endpoints"
tags: ["Account"]
---

# Register a webhook endpoint

`POST /v1/webhook-endpoints`

## Request

- Base URL: `https://api.ufcalendar.com`
- URL: `https://api.ufcalendar.com/v1/webhook-endpoints`
- Auth: HTTP bearer

Pro+. Body: `{"url": "https://...", "events": ["event.announced", "fight.result", "card.changed", "event.completed", "odds.moved"]}`. Subscribe to `event.announced` and you do not need to poll `/v1/events` to discover new cards — it fires once, within a minute of a card first landing in our data, and carries the same event object the list endpoint returns. `fight.result` fires once per bout within a minute of the result, as `revision` 1, and again as the next revision (`corrected: true`, with the `previous` result) whenever the winner, method, round, time or status is corrected in the 72 h after it — upsert on `fight_id` and keep the highest `revision`. `odds.moved` fires when the UFCalendar consensus line on an upcoming bout moves at least 5 implied-probability points on corner a, or the favourite flips, measured against the last line we delivered for that bout (its opening line before the first delivery), so a slow drift arrives once, not per refresh: `data` is `{fight_id, event, fighter_a, fighter_b, odds: {consensus, opening, movement, updated_at}, previous, delta_points_a, direction, favourite_flipped, moved_at}`. Information only, not betting advice. The response contains the signing `secret` ONCE. Every delivery body is `{"id", "type", "data", "sent_at"}` (`WebhookDelivery` schema) — `id` is stable across retries, so deduplicate on it. Deliveries carry `X-UFCalendar-Signature: t=<unix>,v1=<hmac>` where the HMAC-SHA256 input is `"<t>.<rawBody>"`. **Verify the timestamp too**: reject anything where `|now - t|` exceeds 300 seconds, otherwise a captured delivery can be replayed at you forever with a valid HMAC. Delivery is at-least-once — expect a retry roughly every 60s until your endpoint returns 2xx. Endpoints auto-disable after 20 consecutive failures, and you may hold at most 3 active endpoints (409 `endpoint_limit`). Latency: results are dispatched within ~1 minute of the result being recorded. `event.announced` only fires for cards that have not started yet, so archive backfills never reach you as new-card noise. `card.changed` is filtered the same way: a change whose before and after read identically (a venue row re-minted for the same arena, a source dropping a sub-time it never knew) is never delivered, and when a source flip-flops a value we deliver the move and its undo once, not the twentieth repeat. Deliveries are free — they never count against your monthly request quota, only the calls you make to manage endpoints do.

## Request body

- object
  - `url` string, uri, required: Must be `https://`.
  - `events` WebhookKind[]: Defaults to every kind when omitted.

## Response `201`

Created — includes the one-time secret

- WebhookEndpointCreatedResponse
  - `data` WebhookEndpointCreated, required
    - `id` integer, required
    - `url` string, required
    - `events` WebhookKind[], required
    - `secret` string, required: `whsec_…` — shown ONCE. HMAC-SHA256 key for `X-UFCalendar-Signature`.
  - `meta` EmptyMeta, required: `meta` is always present; `{}` when the endpoint has nothing to add.
    - `ignored_params` string[]: Present only when the request carried query parameters this endpoint does not take — they were ignored. Same list as the `X-UFCalendar-Ignored-Params` header. Sorted, unique.

## Other responses

- `400`: Invalid input (`invalid_json`, `invalid_url`, `invalid_events`).
- `401`: Missing/invalid API key (`unauthorized`).
- `403`: Requires Pro or above (`tier_required`), or a plan bought directly rather than through RapidAPI (`direct_plan_required`).
- `404`: Not found (`not_found`; staged-org data 404s until the org is promoted).
- `409`: At most 3 active endpoints (`endpoint_limit`).
- `429`: Rate limited (`rate_limited`) or monthly quota reached (`quota_exceeded`). Honor `Retry-After`.
- `500`: Something broke on our side (`internal_error`). It has been logged under the `request_id`.

---

[API](https://skmtc.dev/ufcalendar/apis/ufcalendar-fight-api.md) · [All operations](https://skmtc.dev/ufcalendar/apis/ufcalendar-fight-api/llms.txt) · [OpenAPI document](https://skmtc.dev/ufcalendar/apis/ufcalendar-fight-api/revisions/9623cc8fb70b?raw)
