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

# Incoming Webhooks

> Create releases and subscribers in Onset by sending a JSON POST from CI, a deploy script, or any external service.

An incoming webhook gives you a URL that turns an HTTP POST into an object in your
workspace. Point your CI pipeline, deploy script, or any tool that can send a
webhook at it, and Onset creates the release or subscriber for you.

Unlike the [REST API](https://onset.io/docs/developers/api-reference/introduction.md), there's no API key
to manage. The secret is baked into the URL, so a service that can only send "a
POST to a URL" — GitHub Actions, Zapier, a shell script — can write to Onset.

There are two types:

| Type | What a POST creates |
| - | - |
| **Release** | A new release, saved as a **draft**. |
| **Subscriber** | A new subscriber, optionally added to lists. |

## Create a webhook

**1. Open developer settings**

In the dashboard, go to **Settings → Developers → Incoming Webhooks**.

**2. Create the webhook**

Click **Create webhook**, give it a name, and pick the event type. The type
can't be changed afterwards — create a second webhook if you need both.

**3. Copy the URL**

Copy the endpoint from the **Webhook Endpoint** column and store it as a
secret in whatever service will call it.

Your URL looks like this:

```
https://webhooks.onset.io/v1/{webhook_id}/{secret}
```

> [!WARNING]
>
> The URL **is** the credential — anyone holding it can write to your workspace.
> Store it as a secret, never commit it, and don't put it in client-side code or
> logs. If it leaks, delete the webhook and create a new one.

## Sending a request

Send a `POST` with a JSON body and `Content-Type: application/json`. No
authentication headers are needed.

The body uses the same field names as the equivalent REST API endpoint, so
anything you can build with the API you can send here.

> [tab] Release
>
> ```bash theme={null}
> curl -X POST https://webhooks.onset.io/v1/{webhook_id}/{secret} \
>   -H "Content-Type: application/json" \
>   -d '{
>     "title": "Search and export improvements",
>     "slug": "search-and-export-improvements",
>     "version": "2.4.0",
>     "body": "This release focuses on **search quality** and CSV exports.",
>     "changes": [
>       { "title": "Added", "content": "CSV export for subscriber lists" },
>       { "title": "Fixed", "content": "Search now matches partial words" }
>     ],
>     "is_public": true
>   }'
> ```
>
> | Field | Type | Notes |
> | - | - | - |
> | `title` | string | **Required.** 3–255 characters. |
> | `slug` | string | **Required.** Lowercase letters, numbers and hyphens, 3–255 characters. A suffix is appended if it's already taken. |
> | `body` | string | Markdown or HTML. |
> | `version` | string | Free-form version label, e.g. `2.4.0`. |
> | `changes` | array | Labelled one-liners rendered as badges above the body. See [Change types](#change-types). |
> | `is_public` | boolean | Defaults to `false`. |
> | `is_pinned` | boolean | Defaults to `false`. |
> | `is_pre_release` | boolean | Defaults to `false`. |
> | `project_id` | string | ID of a [project](https://onset.io/docs/guides/projects.md). |
> | `label_ids` | string\[] | IDs of [labels](https://onset.io/docs/guides/labels.md). |
> | `contributor_ids` | string\[] | IDs of [contributors](https://onset.io/docs/guides/releases/contributors.md). |
> | `hero` | object | `{ "type": "image" \| "video", "url": "..." }`. Omit for none — `null` is not accepted. |
> | `attachments` | array | File or link attachments. |
>
> The response is the created release, in the same shape the REST API returns.

> [tab] Subscriber
>
> ```bash theme={null}
> curl -X POST https://webhooks.onset.io/v1/{webhook_id}/{secret} \
>   -H "Content-Type: application/json" \
>   -d '{
>     "email": "ada@example.com",
>     "list_ids": ["clx1a2b3c4d5e6f7g8h9i0j1"]
>   }'
> ```
>
> | Field | Type | Notes |
> | - | - | - |
> | `email` | string | **Required.** Must be a valid email address. |
> | `list_ids` | string\[] | IDs of [subscriber lists](https://onset.io/docs/guides/subscribers/lists.md) to add them to. |
>
> The response is the created subscriber, including the lists they joined.

### Releases arrive as drafts

A release created through an incoming webhook is always saved with status
`DRAFT`, even if `is_public` is `true`. Nothing appears on your public page,
and no subscriber emails or Slack/Discord announcements go out, until someone
publishes it from the dashboard or via the API.

That's deliberate: it lets a deploy pipeline stage release notes automatically
while a human still decides when customers see them. See
[Publishing](https://onset.io/docs/guides/releases/publishing.md).

### Change types

Each entry in `changes` must use the `title` of a change type configured in your
workspace (**Settings → Releases**), matched case-insensitively. The badge colour
comes from that configuration, so it isn't accepted in the payload.

An unrecognised title returns `400` with the list of titles your workspace
accepts, so you can correct the call without a second lookup. If you'd rather not
deal with change types, leave `changes` out and write everything into `body`.

## Responses

A successful call returns `200` with the created object.

| Status | Meaning |
| - | - |
| `400` | The payload failed validation. The `errors` object maps each field to its problems. |
| `402` | Creating this subscriber would exceed your plan's subscriber limit. |
| `404` | The webhook ID and secret don't match an existing webhook — usually a truncated or rotated URL. |
| `410` | A milestone webhook. See below. |

Errors have a consistent shape:

```json theme={null}
{
  "success": false,
  "message": "Invalid request",
  "errors": {
    "title": ["String must contain at least 3 character(s)"]
  }
}
```

## Example: publish from GitHub Actions

Draft a release in Onset whenever you tag a version:

```yaml theme={null}
name: Draft release notes

on:
  push:
    tags: ['v*']

jobs:
  onset:
    runs-on: ubuntu-latest
    steps:
      - name: Create Onset draft
        run: |
          VERSION="${GITHUB_REF_NAME#v}"
          curl -sSf -X POST "$ONSET_WEBHOOK_URL" \
            -H "Content-Type: application/json" \
            -d "$(jq -n --arg v "$VERSION" '{
              title: ("Release " + $v),
              slug: ("release-" + ($v | gsub("\\."; "-"))),
              version: $v,
              is_public: true
            }')"
        env:
          ONSET_WEBHOOK_URL: ${{ secrets.ONSET_WEBHOOK_URL }}
```

The draft lands in your dashboard for editing before you publish it.

## Managing webhooks programmatically

Incoming webhooks can also be created, listed, updated and deleted through the
[REST API](https://onset.io/docs/developers/api-reference/introduction.md) under `/incoming-webhooks`, or
with the Node SDK:

```javascript theme={null}
const webhook = await onset.incomingWebhooks.create({
  name: 'CI deploy notes',
  type: 'RELEASE',
});
```

## Milestone webhooks

Milestone incoming webhooks can no longer be created, and calls to existing ones
return `410 Gone`. The roadmap is being deprecated and will be disabled on
**October 5**. Existing milestone webhooks stay visible in settings so you can
find and delete them.

> [!NOTE]
>
> Incoming webhooks are for sending data **into** Onset. To receive notifications
> **from** Onset when something changes, see [Webhooks](https://onset.io/docs/developers/webhooks.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.