---
title: "Create an account-level recipient"
method: POST
path: "/api/v1/recipients"
---

# Create an account-level recipient

`POST /api/v1/recipients`

## Request

- Base URL: `https://api.uptimemonitoring.com`
- URL: `https://api.uptimemonitoring.com/api/v1/recipients`
- Auth: HTTP bearer

`type` is `webhook`, `email`, `slack`, or `pagerduty`. For
`type=webhook`, the response includes `secret` — shown exactly
once, here, and never again on any subsequent read; `config.url`
must point to a public hostname (SSRF hardening, same rule as the
deprecated webhook alias). For `type=email`, the response carries
no `secret` (every email recipient shares the fixed empty string
internally); `config.address` is required instead of `config.url`,
and the recipient starts `status=pending` until the mailed
confirmation link is followed — see `POST /email/confirm/{token}`
and `POST /api/v1/recipients/{recipientID}/confirm/resend`. For
`type=slack` (issue #359), `config.url` must be a Slack incoming
webhook URL in the exact shape
`https://hooks.slack.com/services/{team}/{bot}/{token}` — no port,
userinfo, query string, or fragment, and no trailing slash;
`config.channel` is optional (e.g. `"#ops"` or `"@alice"`) and
overrides the incoming webhook's own default channel. The response
never carries `secret` for a slack recipient, and `config.url` is
always masked to `https://hooks.slack.com/services/{team}/{bot}/********`
(or, for a stored URL that doesn't match the expected shape, the
bare `https://hooks.slack.com/services/********`) — the real token
is never echoed back. For `type=pagerduty` (issue #363),
`config.integration_key` is required instead of `config.url`: the
PagerDuty Events API v2 routing/integration key, exactly 32
characters of `[A-Za-z0-9]`. The response never carries `secret`
or `config.url` for a pagerduty recipient, and `config.integration_key`
is always masked to `"********"` plus the last 4 characters (or the
bare `"********"` for a stored key that doesn't match the expected
shape) — the real key is never echoed back. Every trigger/resolve
for one outage shares one `dedup_key`
(`"<monitor_id>-<outage_id>"`), but only `dedup_key` and
`payload.custom_details.monitor_id` are stable across an entire
outage — `payload.source`, `payload.custom_details.monitor_name`,
and the resolve's own fields do not repeat the trigger's `event`
or `reason`. If `config.integration_key` is an Event Orchestration
routing key, route its rules on `custom_details.monitor_id`, or
enable the service's Global Dedup setting, rather than on any
field that can change mid-outage. `config` validates
only the four reserved keys — `url`, `address`, `channel`,
`integration_key` — against the chosen `type`: in any letter case,
and regardless of value (including `null` or `""`), a reserved key
the `type` does not allow (e.g. `config.address` on a webhook,
slack, or pagerduty recipient, `config.integration_key` on any
type but pagerduty) is rejected with 400, as are two keys that
fold to the same reserved name (e.g. `url` and `URL`). Any other
key in `config` is not one of the four reserved names and is
silently ignored. `name` is required (max 255 chars); `events`
defaults to `["down","up","flapping"]` when omitted and must be a
non-empty subset of that set when provided.

For `type=pagerduty`: deleting or detaching the recipient,
changing `config.integration_key`, or deleting or disabling the
monitor or the account does NOT resolve an incident already open
in PagerDuty under the old key — only the customer, or the
PagerDuty service's own auto-resolve, can close it. A delivery
already queued before a monitor is disabled or an account is
suspended can still send.

## Request body

- RecipientCreate
  - `name` string, required
  - `type` 'webhook' | 'email' | 'slack' | 'pagerduty', required
  - `config` object, required: `{"url": "..."}` for `type=webhook`, `{"address": "..."}` for `type=email`, `{"url": "...", "channel": "..."}` (channel optional) for `type=slack`, `{"integration_key": "..."}` for `type=pagerduty`. A reserved key the chosen `type` does not allow — `address` on webhook/slack/pagerduty, `url`/`channel` on email or pagerduty, `channel` on webhook, `integration_key` on any type but pagerduty — is 400, matched case-insensitively and regardless of value (including `null` or `""`); two keys that fold to the same reserved name (e.g. `url` and `URL`) are also 400. For `type=slack`, `config.url` must match `https://hooks.slack.com/services/{team}/{bot}/{token}` exactly (no port, userinfo, query string, or fragment, no trailing slash). For `type=pagerduty`, `config.integration_key` must be exactly 32 characters of `[A-Za-z0-9]`.
    - `url` string, uri
    - `address` string, email
    - `channel` string: `type=slack` only. Optional; e.g. "#ops" or "@alice".
    - `integration_key` string: `type=pagerduty` only. The PagerDuty Events API v2 routing/integration key, exactly 32 characters of `[A-Za-z0-9]`.
  - `default` boolean
  - `events` string[]: Defaults to `["down","up","flapping"]` when omitted.

## Response `201`

Created, with `secret`

- Recipient: Account-level, named delivery target (issue #212) assignable to any number of monitors via `PUT /api/v1/monitors/{monitorID}/recipients`. `secret` is included ONLY on the `POST /api/v1/recipients` response for `type=webhook` — never on `GET`/`PATCH`, and never for `type=email`, `type=slack`, or `type=pagerduty`. The signed JSON delivery body for `webhook` is unchanged from the pre-#212 `Webhook` contract; see its description. `type=email` (issue #321) sends outage/recovery alert mail instead of a signed POST — see `status` below for its confirmation lifecycle. `type=slack` (issue #359) posts an unsigned Block Kit message to a Slack incoming webhook URL; `config.url` is always masked (the real token is never echoed). `type=pagerduty` (issue #363) posts to the PagerDuty Events API v2; `config.integration_key` is always masked (the real key is never echoed).
  - `id` integer
  - `name` string
  - `type` 'webhook' | 'email' | 'slack' | 'pagerduty'
  - `config` object: Channel-specific config: `url` for `type=webhook` or `type=slack`, `address` for `type=email`, `integration_key` for `type=pagerduty`, plus an optional `channel` for `type=slack`. For `type=slack`, `url` is always the masked form `https://hooks.slack.com/services/{team}/{bot}/********` (or the bare `https://hooks.slack.com/services/********` for a stored URL that doesn't match the expected shape) — the real token is never returned. For `type=pagerduty`, `integration_key` is always the masked form `"********"` plus the last 4 characters (or the bare `"********"` for a stored key that doesn't match the expected shape) — the real key is never returned.
    - `url` string, uri
    - `address` string, email: Normalized (lowercased) email address.
    - `channel` string: `type=slack` only. Optional channel/user override (e.g. `"#ops"` or `"@alice"`); omitted when unset.
    - `integration_key` string: `type=pagerduty` only. Masked — see this object's own description above.
  - `default` boolean: When true, this recipient is attached automatically to every new monitor created on this account, at create time, in the same transaction as the monitor row. A monitor born down does NOT notify a newly-attached default recipient at creation — the create response already shows the monitor's state directly, and the default's first notification is the monitor's next qualifying transition.
  - `status` 'active' | 'pending' | 'disabled': `pending`: a `type=email` recipient awaiting confirmation (no alerts sent yet). `disabled`: unsubscribed (via the mailed unsubscribe link) — alerts stopped; re-enabling requires deleting and recreating the recipient, which re-confirms from scratch. `active`: every `type=webhook`, `type=slack`, and `type=pagerduty` recipient, and a confirmed, non-disabled `type=email` recipient.
  - `events` string[]: Non-empty subset of event types this recipient is notified for.
  - `monitor_ids` integer[]: Monitors this recipient is currently attached to. `[]` for none.
  - `secret` string: HMAC-SHA256 signing secret, prefix `whs_live_`. Present ONLY on the `POST /api/v1/recipients` response for `type=webhook` (and the deprecated `GET /api/v1/monitors/{monitorID}/webhook` alias, for compatibility) — omitted everywhere else, and always omitted for `type=email`, `type=slack`, and `type=pagerduty` (neither a slack nor a pagerduty recipient has a secret at all — the incoming webhook URL or the routing key itself is the credential, and both live in `config`, masked).
  - `created_at` string, date-time
  - `updated_at` string, date-time

## Other responses

- `400`: Bad request
- `413`: Request body exceeds the 64 KB hard limit. Per docs/SPEC.md §Hard Limits the dispatcher rejects any payload larger than 65,536 bytes before it is decoded.
- `415`: The request `Content-Type` is not `application/json`. Write operations (POST, PUT, PATCH) require that header; a missing or non-JSON media type is rejected before the body is read. The `charset` parameter is accepted and ignored, so `application/json; charset=utf-8` is valid.
- `429`: Per-account dispatcher rate limit exceeded (200 requests/minute). The `Retry-After` header gives the integer seconds until at least one token has refilled. The `X-RateLimit-*` trio reflects the bucket whose rejection produced this response — usually the global 200/min cap; on `POST /api/v1/monitors` a 429 may instead reflect the per-route 20/min creation limit.

---

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