---
title: "Create a webhook"
method: POST
path: "/api/v1/webhooks"
tags: ["Webhooks"]
---

# Create a webhook

`POST /api/v1/webhooks`

## Request

- Base URL: `https://everypage.co`
- URL: `https://everypage.co/api/v1/webhooks`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://everypage.co/oauth/authorize`, token URL `https://everypage.co/oauth/token`, scopes: `webhooks:manage`)

Register an HTTPS endpoint to receive signed event deliveries (available on every plan; up to 25 webhooks per account). Set `fileUuid` to scope delivery to one file; omit it for account-wide delivery. The signing secret (`whsec_...`) is returned exactly once, in this response, and is never retrievable again. Deliveries carry an `X-Everypage-Signature` header: `t=<unix ts>,v1=<hex>` where v1 is HMAC-SHA256 over `<t>.<raw body>` keyed with the FULL secret string (the `whsec_` prefix is part of the key). Endpoints failing 20 consecutive deliveries are disabled automatically.

Payload note: when the viewer arrived through a link variant, the `file.opened`, `file.viewed` and `file.downloaded` data objects additionally carry `variantUuid` and `variantLabel` (the label is omitted for variants whose label was GDPR-redacted). Both keys are additive - canonical-link events carry neither. `file.opened` fires the moment a non-owner reading session starts, before any page is read, and shares its `sessionId` with the `file.viewed` event that follows at session end.

Plan note: `gate.completed` deliveries require the Pro plan at event time; other kinds deliver on every plan.

## Request body

- WebhookCreate
  - `url` string, required: Plain HTTPS URL (no credentials, no private/local addresses).
  - `events` string[], required: Event kinds to deliver (at least one). `gate.completed` deliveries require the Pro plan at event time; other kinds deliver on every plan.
  - `format` 'json' | 'slack'
  - `fileUuid` string: Optional: deliver only this file's events (UUID or short id; must be a file you own).

## Response `201`

Created; `secret` is shown only here.

- object
  - `webhook` Webhook
    - `uuid` string, uuid
    - `url` string
    - `events` string[]
    - `format` 'json' | 'slack'
    - `active` boolean: False only after automatic disable for repeated delivery failures.
    - `consecutiveFailures` integer
    - `createdAt` string, date-time
    - `fileUuid` string, uuid: Present when delivery is scoped to one file; absent for account-wide webhooks.
  - `secret` string

## Other responses

- `400`: Invalid body; `url` not a plain HTTPS URL (or pointing at a private/local address, or over 2048 chars); empty or unknown `events`; bad `format`; or the 25-webhooks-per-account limit reached (code `validation_error`).
- `401`: No or invalid API key, OAuth token, manage token or claim token (code `missing_token` or `invalid_token`).
- `403`: Not the owner (code `forbidden`), an OAuth token missing the required scope (`insufficient_scope`, with a `WWW-Authenticate` challenge naming it), a feature above your plan (`plan_required`), a plan limit reached such as file slots or the per-file asset cap (`forbidden`), or - on the two manage-token operations - a token acting at the free tier on a plan-gated field.
- `404`: `fileUuid` does not name a file you own.
- `413`: The upload (or the PDF a Word/PowerPoint file converted to) is above your plan's size cap - 20MB Free, 200MB Basic, 2GB Pro - or a JSON body is over its fixed cap (1MB for settings and hotspots, 64KB for variants and claims) (code `payload_too_large`).
- `429`: Over the limit - 120 requests per minute per Authorization value on /api/v1 (webhook test 10/min; the Canva lane 30/min per IP). Code `rate_limited`; wait for `Retry-After`.

---

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