---
title: "Ingest Webhook"
description: "The per-source ingest URL your monitoring tools post alerts to: authentication, limits and status codes."
---

`POST /api/im/ingest/:token`

Every webhook [alert source](/api/incident-management/alert-sources-api) has its own ingest URL. Your monitoring tool posts its alert JSON there, and the source's mapping turns it into an alert in Incident Management. The [setup guides](/api/incident-management/alert-sources) show the configuration for Zabbix, Datadog, Grafana, Prometheus Alertmanager, Sentry and custom webhooks.

To send alerts from your own code with an organization-wide API token instead, use [Events Ingest](/api/incident-management/events-ingest).

## Authentication

The token in the URL **is** the credential: treat the full URL like a password. It is returned once when the source is created and when it is [rotated](/api/incident-management/alert-sources-api); after a rotation the previous token keeps working until `secondaryExpiresAt`.

A source can additionally require one or both of:

- **Bearer token**: `Authorization: Bearer <source bearer token>`.
- **HMAC signature**: `X-Uptimeify-Timestamp` (Unix seconds) and `X-Uptimeify-Signature` = hex HMAC-SHA256 of `<timestamp>.<raw body>` with the source's HMAC secret, optionally prefixed `sha256=`. The timestamp must be within 5 minutes of the server time.

Every authentication failure, including an unknown token, answers the same `404`, so a wrong token cannot be told from a wrong signature.

## Request

- Body: JSON, at most 256 KB, nested at most 20 levels deep. An empty body counts as `{}`.
- Content type is not checked; the body is parsed as JSON.
- Which fields matter is decided by the source's mapping (preset or custom). The body below fits a custom webhook source mapped to these field names.

### Example (cURL)

```bash
curl -X POST "https://uptimeify.io/api/im/ingest/$INGEST_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title":"Disk almost full on db-1","severity":"high","status":"firing","dedup_key":"db-1-disk"}'
```

With HMAC:

```bash
TS=$(date +%s)
BODY='{"title":"Disk almost full on db-1","status":"firing"}'
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$HMAC_SECRET" -hex | sed 's/^.* //')
curl -X POST "https://uptimeify.io/api/im/ingest/$INGEST_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Uptimeify-Timestamp: $TS" \
  -H "X-Uptimeify-Signature: sha256=$SIG" \
  -d "$BODY"
```

## Response

`202 Accepted`:

```json
{ "accepted": true }
```

The payload is accepted for processing; mapping, deduplication and paging happen asynchronously. Use the source's [stats and recent payloads](/api/incident-management/alert-sources-api) to see what was made of it.

## Limits

- 300 requests per minute per source
- 50 MB of request bodies per hour per organization

## Status codes

| Status | Body | Meaning |
|--------|------|---------|
| `202` | `{"accepted":true}` | Accepted. |
| `404` | `{"error":"not_found"}` | Unknown or rotated-out token, failed Bearer or HMAC check, or the organization was deleted. |
| `413` | `{"error":"payload_too_large"}` | Body larger than 256 KB. |
| `422` | `{"error":"invalid_json"}` | Body is not valid JSON. |
| `422` | `{"error":"payload_too_deep"}` | JSON nested deeper than 20 levels. |
| `429` | `data.code` `imIngestRateLimited` | More than 300 requests per minute for this source. |
| `429` | `data.code` `imIngestByteBudgetExceeded` | Hourly byte budget of the organization used up. |
| `503` | `{"error":"unavailable"}` | Temporarily unable to accept; retry with backoff. Your tool's own retry is the right answer. |
