Monitive Webhook Deliveries API

The Webhook Deliveries API from Monitive — 1 operation(s) for webhook deliveries.

Operations 1

GET /api/v1/webhook-deliveries List recent webhook delivery attempts for the authenticated account #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/uptimemonitoring-com:uptimemonitoring-com-webhook-deliveries-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

uptimemonitoring-com-webhook-deliveries-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: UptimeMonitoring.com Webhook Deliveries API
  version: 0.1.0
  description: 'API-first uptime monitoring for developers, deploy pipelines, and AI agents.


    ## Authentication


    All `/api/v1` endpoints require a Bearer token — either an API key (`umk_live_*`)

    or a session token obtained via GitHub OAuth. The `/mcp` endpoint additionally

    accepts OAuth 2.1 JWT access tokens (`eyJ...`) issued via the self-hosted

    authorization server documented under the `OAuth` tag.


    ```

    Authorization: Bearer umk_live_abc123...

    ```


    ## Rate limits


    200 requests/minute per account. Exceeding the limit returns `429` with a

    `Retry-After` header.


    Responses that pass through the per-account rate-limiter carry the

    `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset`

    headers (decimal integers; `Reset` is a Unix epoch second at which

    the bucket would be full again). In practice this is every

    authenticated response under `/api/v1/*` whose request reached the

    handler — earlier failures from the auth middleware (e.g. 401 missing

    credentials, 403 suspended account) return before the limiter runs

    and do NOT carry the trio. Routes mounted outside `/api/v1`

    (notably `GET /api/v1/events/stream`, which has its own SSE

    middleware stack) are not subject to this limiter and likewise do

    not emit these headers. Successful responses on

    `POST /api/v1/monitors` reflect the **global** 200/min bucket; the

    per-route 20/min creation limit only surfaces its own headers when

    it 429s. The trio is documented once here rather than referenced

    into every individual response — see `components.headers` for the

    schemas.


    One exception: once a request presenting an API key enters an

    authenticated route group through a registered route or mounted

    subtree, while that key remains in the bounded pre-auth cache

    (entries expire after 5 minutes, but may be evicted sooner once the

    cache is at capacity), the global bucket is charged BEFORE

    authentication and authorization run (issue #47) — so for that

    cached key only, a `401` (key revoked while still cached), `403`,

    `500`, or a `404` for an unmatched path inside a mounted subtree

    also carries the `X-RateLimit-*` trio and spends one token. `429`

    still comes first whenever the bucket is already empty. An uncached

    key (first seen, expired, or evicted early at capacity), a session

    token, an OAuth JWT, and any path outside every authenticated group

    are all unaffected.


    ## Browser push notifications


    Subscriptions registered via `POST /api/v1/push/subscribe` receive

    fire-and-forget Web Push notifications on monitor state transitions

    (down/up/flapping); per-monitor 5-minute throttle. No public API change

    in this release — dispatch is internal to `internal/push.Dispatcher`.'
  contact:
    name: UptimeMonitoring.com
    url: https://uptimemonitoring.com
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
servers:
- url: https://api.uptimemonitoring.com
  description: Production
tags:
- name: Webhook Deliveries
paths:
  /api/v1/webhook-deliveries:
    get:
      operationId: listWebhookDeliveries
      summary: List recent webhook delivery attempts for the authenticated account
      description: 'Two independent response shapes, selected by `?order=`:


        - Default (`order` key absent entirely): offset mode. `created_at`

        descending, `?limit=`/`?offset=`, `WebhookDeliveryListResponse`.

        Unchanged since before issue #396 apart from the added

        `enqueued_at` field on each delivery. A present `order` key —

        even an empty `?order=` or a bare `?order` — never falls back

        to this mode; see below.

        - `order=id`: cursor mode (issue #396). `id` ascending,

        keyset-paginated via `?cursor=`, `WebhookDeliveryCursorListResponse`.

        Any `?offset=` (any value, including `0`) is rejected with 400.

        A malformed or empty `?cursor=` is rejected with 400 — presence

        of the parameter is checked independently of its value, so a

        present-but-empty `?order=id&cursor=` is rejected the same as a

        malformed one, not treated as "no cursor". `?cursor=` without

        `order=id` is also rejected with 400, empty value included — a

        cursor only has meaning in keyset order. `order` itself must

        be present exactly once with the value `id`; a present but

        empty `?order=`, a bare `?order`, a repeated `order=` key, or

        any other value is rejected with 400 rather than silently

        running offset mode. A malformed query string itself (e.g. an

        unescaped `;`) is rejected with 400 before any parameter is

        inspected.


        `?recipient_id=` filters to one recipient''s own deliveries

        (`webhook_deliveries.webhook_id`), still scoped to the caller''s

        account. `?status=` is repeatable (OR-union) — a single

        `status=x` behaves exactly as before issue #396.


        **Recipient-queue drain recipe.** To prove a recipient''s

        pre-attachment backlog is drained before deleting it (e.g.

        rotating to a replacement recipient via

        `DELETE /api/v1/recipients/{id}?replaced_by=`): scan

        `?recipient_id={id}&status=pending&status=retrying&order=id`,

        following `next_cursor` until it is `null`. Only an empty scan

        result proves the queue is literally empty; any row the scan

        returns proves the opposite, since the status filter selects

        live rows by construction. The backlog is considered drained

        once every returned row''s `enqueued_at` is non-null and greater

        than the replacement''s `attached_at` on every monitor

        (`GET /api/v1/monitors/{id}/recipients`''s `attached_at` field) —

        but that timestamp comparison alone does not prove sibling

        coverage (that each row has a counterpart delivery enqueued for

        the replacement); it only establishes that a live row postdates

        the replacement''s attachment. `DELETE /api/v1/recipients/{recipientID}`

        is the authoritative check for a safe rotation — it verifies

        sibling coverage server-side, in one transaction, rather than

        trusting a client-computed timestamp comparison. See that

        endpoint for the full recipe and its guarantees/limits.'
      security:
      - bearerAuth: []
      parameters:
      - name: monitor_id
        in: query
        schema:
          type: integer
          format: int64
      - name: recipient_id
        in: query
        description: Filter to one recipient's deliveries (issue
        schema:
          type: integer
          format: int64
      - name: status
        in: query
        style: form
        explode: true
        description: 'Repeatable (issue #396): `status=pending&status=retrying`

          returns the OR-union. A single `status=x` behaves exactly as

          on master.

          '
        schema:
          type: array
          items:
            type: string
            enum:
            - pending
            - retrying
            - delivered
            - failed
            - superseded
            - cancelled
      - name: since
        in: query
        schema:
          type: string
          format: date-time
      - name: limit
        in: query
        schema:
          type: integer
          minimum: 1
          maximum: 100
          default: 50
      - name: offset
        in: query
        description: 'Offset-mode only. Rejected with 400 (any value, including

          `0`) when combined with `order=id`.

          '
        schema:
          type: integer
          minimum: 0
          default: 0
      - name: order
        in: query
        description: 'Omit for the default offset-mode shape. `id` switches to

          cursor mode (issue #396). When present, the parameter must

          appear exactly once with the value `id` — an empty

          `?order=`, a bare `?order`, a repeated `order=` key, or any

          other value is rejected with 400 rather than silently

          falling back to offset mode.

          '
        schema:
          type: string
          enum:
          - id
      - name: cursor
        in: query
        description: 'Cursor-mode only (issue #396) — the opaque `next_cursor` value

          from a previous page. Requires `order=id`.

          '
        schema:
          type: string
      responses:
        '200':
          description: Deliveries
          content:
            application/json:
              schema:
                oneOf:
                - $ref: '#/components/schemas/WebhookDeliveryListResponse'
                - $ref: '#/components/schemas/WebhookDeliveryCursorListResponse'
        '400':
          description: 'Bad request — an unknown `status` value; an `order` that is

            empty, repeated, or anything other than `id` (including a

            bare `?order`); a malformed `cursor`; `cursor` supplied

            without `order=id`; or `offset` supplied together with

            `order=id`.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/RateLimited'
      tags:
      - Webhook Deliveries
components:
  schemas:
    WebhookDelivery:
      type: object
      properties:
        id:
          type: integer
          format: int64
        webhook_id:
          type: integer
          format: int64
          description: 'A recipient id (issue #212) — the field name is kept for wire

            compatibility; it has not named a row in the legacy `webhooks`

            table since the recipients migration. A row can represent an

            email delivery (issue #321), a Slack post (issue #359), or a

            PagerDuty Events API v2 event (issue #363) as well as a

            webhook POST — check the referenced recipient''s `type` to tell

            which; `last_response_code` is SendGrid''s status for email

            (202 on success, 0 on transport error), the destination''s HTTP

            status for webhook, Slack''s own HTTP status for slack

            (`response_body` carries Slack''s error string, e.g.

            `"no_service"`), and PagerDuty''s own HTTP status for pagerduty

            (`response_body` carries FIXED outcome text, e.g.

            `"PagerDuty accepted the event (HTTP 202)"` or

            `"PagerDuty rate-limited the event (HTTP 429)"` — never

            PagerDuty''s actual response body, which could echo the

            routing key) — for both slack and pagerduty, only 429 and 5xx

            retry; every other non-2xx, 3xx included, is terminal on the

            first attempt. A pagerduty row with no `outage_id` — enqueued

            before issue #363, or an "up" recovery whose onset transition

            left no open incident to inherit an outage id from — fails

            immediately with `response_body` `"no outage key"` and no

            request ever sent; such a recovery is never reported to

            PagerDuty.

            '
        monitor_id:
          type: integer
          format: int64
        account_id:
          type: integer
          format: int64
        event_type:
          type: string
          example: down
        payload_hash:
          type:
          - string
          - 'null'
        status:
          type: string
          enum:
          - pending
          - retrying
          - delivered
          - failed
          - superseded
          - cancelled
          description: '`superseded` (added for issue #165): an older `pending`/`retrying`

            delivery for the same webhook+monitor pair was replaced by a

            newer one, bounding a swarm of stale notifications after an

            outage recovers to at most one live delivery per pair at a

            time. Not immutably terminal: the

            replacement is not atomic with dispatch, so a delivery already

            in flight when it was superseded can still complete and

            transition to `delivered` for that one attempt — a subscriber

            can still receive a delivery for a transition that was

            superseded before it arrived.


            `cancelled` (added for issue #338): a delayed `down` delivery

            whose outage recovered before its `down_delay_s` elapsed, or

            whose recipient''s events narrowed to drop `down` while the

            row was still pending. Never reused for `superseded` — nothing

            replaced the row, it was simply never sent. Also not

            immutably terminal: a row already claimed for dispatch when an

            events-narrowing edit cancels it can still complete and

            transition to `delivered`.

            '
        attempt_count:
          type: integer
        collapsed_count:
          type: integer
          description: 'The number of older live (`pending`/`retrying`) delivery rows

            this row superseded, plus their own accumulated collapsed

            counts. Zero for a delivery that superseded nothing. This is

            NOT a count of POSTs guaranteed suppressed, and NOT a count of

            transitions the subscriber missed: a superseded row can still

            be in flight and reach `delivered` after being counted here.

            Mirrors the outbound `collapsed_transitions` payload field.

            '
        next_attempt_at:
          type: string
          format: date-time
        last_attempt_at:
          type:
          - string
          - 'null'
          format: date-time
        last_response_code:
          type:
          - integer
          - 'null'
        response_body:
          type:
          - string
          - 'null'
          description: 'Truncated copy (≤4096 bytes, NUL-stripped, valid UTF-8) of the

            subscriber''s HTTP response body from the last attempt, for

            `type=webhook`/`type=email`/`type=slack`. Present to help

            debug rejected deliveries; null for rows recorded before this

            was captured or when the subscriber returned no body. For

            `type=pagerduty` (issue #363), this is NEVER the actual

            response body — it is always one of a small set of fixed

            outcome strings (e.g. `"PagerDuty accepted the event (HTTP

            202)"`, `"PagerDuty rejected the event (HTTP 400)"`,

            `"PagerDuty request failed (timeout)"`, or `"no outage key"`),

            because a real PagerDuty response body could echo the request

            back, including the routing key.

            '
        created_at:
          type: string
          format: date-time
        cancelled_at:
          type:
          - string
          - 'null'
          format: date-time
          description: Set only when status is `cancelled` (issue
        enqueued_at:
          type:
          - string
          - 'null'
          format: date-time
          description: 'The Postgres server clock''s own stamp of this row''s insert

            moment (issue #396) — distinct from `created_at`, which is

            the dispatcher''s own Go-clock transition time and so cannot

            be compared against anything the database generates. Null

            for a row written before this column existed; every reader

            treats null as "before any boundary". Used by the

            `?recipient_id=&order=id` drain scan (see

            `/api/v1/webhook-deliveries`''s description) and the guarded

            `DELETE /api/v1/recipients/{recipientID}?replaced_by=`

            endpoint to decide whether a row predates a replacement

            recipient''s attachment.

            '
    Error:
      type: object
      required:
      - error
      properties:
        error:
          type: string
      example:
        error: something went wrong
    WebhookDeliveryCursorListResponse:
      type: object
      description: 'The response shape for `?order=id` (issue #396) — keyset-paginated,

        ascending by `id`. No `total`/`offset`: cursor mode never runs the

        COUNT(*) that would produce one. `next_cursor` is always present,

        even on the last page, where its value is `null`.

        '
      required:
      - deliveries
      - limit
      - next_cursor
      properties:
        deliveries:
          type: array
          items:
            $ref: '#/components/schemas/WebhookDelivery'
        limit:
          type: integer
        next_cursor:
          type:
          - string
          - 'null'
          description: 'Opaque base64url token. Pass back verbatim as `?cursor=` to

            fetch the next page. `null` means no further page exists.

            '
    WebhookDeliveryListResponse:
      type: object
      properties:
        deliveries:
          type: array
          items:
            $ref: '#/components/schemas/WebhookDelivery'
        total:
          type: integer
        limit:
          type: integer
        offset:
          type: integer
  responses:
    RateLimited:
      description: '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.

        '
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds until the next allowed request.
        X-RateLimit-Limit:
          $ref: '#/components/headers/XRateLimitLimit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/XRateLimitRemaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/XRateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: account rate limit exceeded; retry in 1s
  headers:
    XRateLimitReset:
      schema:
        type: integer
      description: 'Unix epoch seconds at which the bucket would be full again if no

        further requests are made. Sub-second fragments are rounded up so

        `Reset - now` is never less than the actual refill duration.

        '
    XRateLimitLimit:
      schema:
        type: integer
      description: 'Bucket capacity for the limiter that produced this response. `200`

        for the global per-account dispatcher cap; `20` on a 429 from the

        per-route monitor-create limiter.

        '
    XRateLimitRemaining:
      schema:
        type: integer
      description: 'Tokens left in the bucket after this request was charged

        (floor of the post-charge token count). `0` on a 429 means the

        request was rejected with the bucket empty.

        '
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: 'API key (`umk_live_*`) or session token from OAuth exchange.

        '