---
title: "Create a webhook"
method: POST
path: "/v1/webhooks"
---

# Create a webhook

`POST /v1/webhooks`

## Request

- Base URL: `https://api.ploti.ai`
- URL: `https://api.ploti.ai/v1/webhooks`
- Auth: HTTP bearer

Have Ploti POST to an https URL whenever one of the member's data or research requests changes: a dataset delivered or failed, a question waiting on their answer, a research request's report ready or its answer in. Use it to wake a routine or alert the user wherever they are instead of polling. Each POST is JSON {id, type, timestamp, data} (for a Data Feeds subscription's webhook, the feed event itself: {id, type, createdAt, subscriptionId, data}) signed with Standard Webhooks (webhook-id, webhook-timestamp, webhook-signature headers) under the returned secret, which is shown once; the reply carries the webhook's id, url, description, header name and creation time beside it. Docs: https://www.ploti.ai/docs/mcp/webhooks. When the receiver needs its own credential (a routine's bearer key), pass it as header; it is sent with every POST and never shown again. A member may have 20 webhooks. Events: data_request.sourcing, .matched, .checkpoint_released (first results), .ready, .failed, .cancelled, .paid_source, .cycle_refused, .question (Ploti needs the member's answer) and .question_reminder; research_request.report_ready (the data check reported what is there and what is missing), .question (the data check needs the member's answer; data.questions has the questions, and answer_research_request_questions sends the answers), .answered (data.answer) and .needs_attention (the research cannot finish on its own); data.url opens the request in Ploti. dataset.updated: a dataset the member watches (watch_dataset) has a new version; data.id is the dataset, data.version and data.promotedAt the new version, data.changeLog and data.counts what it added, changed and removed (list_dataset_changes lists those rows).

## Headers

- `Idempotency-Key` string

## Request body

- object
  - `url` string, uri, required: The https address Ploti POSTs each event to; it must take JSON from the public internet.
  - `description` string: What the receiver is for, shown beside the webhook in Ploti, e.g. 'Nightly permit digest routine'.
  - `header` object: A header the receiver requires, e.g. {"name": "Authorization", "value": "Bearer …"}.
    - `name` string, required: The header the receiver reads its credential from, such as Authorization.
    - `value` string, required: The credential the receiver expects in that header, such as 'Bearer <token>'. Ploti sends it with every POST and never shows it again.
  - `subscriptionId` string: A Data Feeds subscription: the webhook then receives that subscription's feed.* events instead of request events.
  - `events` string[]: With subscriptionId, only these feed event types; every one when left out.

## Response `201`

Created

- object
  - `webhook` object, required
    - `id` string, required
    - `url` string, required
    - `description` string, nullable, required
    - `headerName` string, nullable, required
    - `subscriptionId` string, nullable, required: The Data Feeds subscription whose events it receives, if any.
    - `events` string[], nullable, required: The Data Feeds event types it receives; every one when null.
    - `createdAt` string, required
    - `lastDelivery` object, nullable, required: The newest event: pending (waiting for its next try), sending, delivered, or failed after its retries.
      - `eventType` string, required
      - `state` 'pending' | 'sending' | 'delivered' | 'failed', required
      - `attempts` integer, required
      - `lastStatus` integer, nullable, required
      - `lastError` string, nullable, required
      - `createdAt` string, required
  - `secret` string, required

## Other responses

- `401`: The credential is missing or not valid.
- `413`: payload_too_large: the body is over 256 KB (2 MB for /v1/tools).
- `415`: unsupported_media_type: a body was sent without Content-Type: application/json.
- `429`: rate_limited: this credential is over its rate. Retry after the Retry-After header's seconds.
- `500`: internal: Ploti failed. Retry; if it keeps happening, contact support.
- `default`: Refused: nothing was done. The code says why and the links say where the person acts on it.

---

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