Incoming webhooks

Post alerts and updates into a channel from Uptime Kuma, Dokploy, CI jobs or any script, using Discord-compatible payloads.

Updated

An incoming webhook is a secret URL that posts messages into one channel. It accepts the same JSON as a Discord webhook, so tools that already send alerts to Discord (Uptime Kuma, Dokploy and many others) can send them to Fishcamp instead, without code.

Create a webhook

Admins create webhooks, one channel at a time:

  1. Open the channel that should receive the messages, for example #alerts.
  2. Choose the gear icon (Channel settings) and go to Incoming webhooks.
  3. Type a name, for example Uptime Kuma, and choose Create.
  4. Copy the URL right away. It's shown only once: Fishcamp keeps only a hash of it.

The URL looks like this:

https://api.fishcamp.space/api/v1/webhooks/inbound/whk_…

The channel's timeline shows who created the webhook. The list under Incoming webhooks shows each webhook's name, the last characters of its token and when it was last used.

The URL is the password

Anyone with the URL can post to the channel. Keep it in a secret or an environment variable, never in a public repository. If it leaks, choose Revoke next to the webhook and create a new one: the old URL stops working at once.

Webhooks post to channels only, not to direct messages.

Send a message

Send a POST with a JSON body to the URL. No other authentication is needed.

curl -X POST "https://api.fishcamp.space/api/v1/webhooks/inbound/whk_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"content": "Nightly backup finished in 4 min."}'

With a card (an "embed"), the same way Discord does it:

curl -X POST "https://api.fishcamp.space/api/v1/webhooks/inbound/whk_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "Deploy bot",
    "content": "Production deploy finished.",
    "embeds": [{
      "title": "web 2.4.1 is live",
      "url": "https://example.com/releases/2.4.1",
      "description": "Build 812, 3 commits since 2.4.0.",
      "color": 5763719,
      "fields": [
        { "name": "Environment", "value": "production", "inline": true },
        { "name": "Duration", "value": "2 min 41 s", "inline": true }
      ],
      "footer": { "text": "GitHub Actions" },
      "timestamp": "2026-11-05T14:32:00Z"
    }]
  }'

Slack-style payloads with text work too:

curl -X POST "https://api.fishcamp.space/api/v1/webhooks/inbound/whk_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"text": "Disk usage on db-1 is above 85%."}'

The message appears under the webhook's name (or the username you sent) with an App label.

Payload

Send content (or text), at least one embed, or both.

FieldTypeDescription
contentstringMessage text, up to 4,000 characters.
textstringSlack-style alternative to content, used when content is missing. Up to 4,000 characters.
usernamestringName shown for this message, up to 80 characters. Defaults to the webhook's name.
avatar_urlstringAccepted for compatibility but not shown: loading images from other sites would reveal readers' IP addresses.
embedsarrayUp to 10 cards, described below.

Each embed:

FieldTypeDescription
titlestringUp to 256 characters.
descriptionstringUp to 4,096 characters.
urlstringAn http(s) link, up to 2,048 characters. Makes the title a link.
colorintegerColor of the bar on the left, as a decimal number from 0 to 16777215 (0xFFFFFF). For example 5763719 is green (#57F287) and 15548997 is red (#ED4245).
fieldsarrayUp to 25 items { "name", "value", "inline" }: name up to 256 characters, value up to 1,024, inline: true puts fields side by side on wide screens.
footerobject{ "text" }: small text under the card, up to 2,048 characters.
timestampstringAn ISO 8601 date, shown next to the footer in the reader's local time.

Text is shown as plain text and links become clickable. Markdown and Discord-only syntax, such as **bold** or <t:1730000000:R> timestamps, appear exactly as sent. Unknown fields are ignored.

Responses

StatusWhen
204 No ContentThe message was posted. This is the default answer, as on Discord.
200 OKThe message was posted and you called the URL with ?wait=true. The body has the message ID and the channel ID.
404 Not FoundThe token is unknown or revoked, or the channel was archived.
413 Payload Too LargeThe body is larger than 512 KB.
415 Unsupported Media TypeThe body isn't JSON. Send it with Content-Type: application/json.
422 Unprocessable EntityNothing to post, or a field breaks a limit. details.errors lists the fields.
429 Too Many RequestsMore than 60 requests in a minute from the same address. Wait and retry.

With ?wait=true:

curl -X POST "https://api.fishcamp.space/api/v1/webhooks/inbound/whk_YOUR_TOKEN?wait=true" \
  -H "Content-Type: application/json" \
  -d '{"content": "Ping"}'
{ "id": "6c1f0f0e-8a7e-4f4e-9d55-0e6f6f1a2b3c", "channel_id": "0b6f3c55-2a51-4e0a-a3d6-6a3f1f2e9d10" }

Errors use the API's error format:

{
  "error": {
    "code": "validation_error",
    "message": "Send \"content\" or \"embeds\"",
    "requestId": "req_5f0c2a9b1e7d4c3a"
  }
}

Uptime Kuma

  1. In Uptime Kuma, open Settings → Notifications → Setup Notification.
  2. Set Notification Type to Discord.
  3. Paste the Fishcamp URL into Discord Webhook URL.
  4. Optionally set Bot Display Name (it becomes the message's name) and a Prefix Custom Message (it becomes the message text above the card). If your version asks where to post, keep Send to channel.
  5. Choose Test: a test message should appear in the channel. Then choose Save and enable the notification on your monitors.

When a monitor goes down or comes back up, Uptime Kuma sends a card with the monitor's name, its URL and the time, plus the error (when down) or the response time (when back up), colored red or green.

Dokploy

  1. In Dokploy, open Settings → Notifications and add a notification.
  2. Choose Discord and paste the Fishcamp URL as the webhook URL.
  3. Pick the events you want, for example app deploys, build errors and database backups.
  4. Use the test button to check that a message arrives, then save.

Dokploy sends a card with the project, the application and a link to the build. Its dates use Discord's <t:…> timestamp syntax, which Fishcamp shows as plain text; the card's own time is shown normally.

From your own scripts and CI

Anything that can send an HTTP request can post. For example, a GitHub Actions step that reports a job's result (store the URL as a repository secret):

- name: Report to #deploys
  if: always()
  env:
    FISHCAMP_WEBHOOK_URL: ${{ secrets.FISHCAMP_WEBHOOK_URL }}
  run: |
    curl -sS -X POST "$FISHCAMP_WEBHOOK_URL" \
      -H "Content-Type: application/json" \
      -d "{\"content\": \"${{ github.workflow }} on ${{ github.ref_name }}: ${{ job.status }}\"}"

GitHub's own repository webhooks send GitHub's event format, which Fishcamp doesn't read. Post from a workflow step, as above, instead.

To post as a person or an AI agent instead of an app, use an API key with the REST API or the MCP server.

On this page