Docket

TROUBLESHOOTING

Your Slack and Discord alerts went quiet

A disabled integration looks exactly like a quiet week. Here is how to tell the difference before you lose a month of reports.

7 MIN READ Last updated 23 August 2026

Assume it is broken. A webhook that stopped delivering produces the same thing on your screen as nobody filing anything, and there is no alert for the absence of an alert. Post a test item to your own board and watch the channel. If nothing arrives, send a message straight to the webhook URL with curl and read the status code, which is where the real answer is.

Detection first, reconnection second. Most people do this the other way round and only discover the outage when a customer asks why their report was ignored.

How do you tell silence from a dead integration?

Two checks, in this order, both under a minute.

First, make the board fire the event. Post a test item, or change a status, or whatever your alert is configured on, and watch the channel. This is the only check that exercises the whole path, including the part where the board decides whether to call the webhook at all.

Second, if nothing arrives, cut the board out and call the endpoint yourself. For Slack:

curl -sS -o /dev/null -w "%{http_code}\n" \
  -H 'Content-type: application/json' \
  -d '{"text":"webhook test"}' \
  "$SLACK_WEBHOOK_URL"

For Discord, and note the query parameter, because it changes the answer you get:

curl -sS -o /dev/null -w "%{http_code}\n" \
  -H 'Content-Type: application/json' \
  -d '{"content":"webhook test"}' \
  "$DISCORD_WEBHOOK_URL?wait=true"

If the direct call works and the board's own event does not, the webhook is fine and the board has switched it off. If the direct call fails, read the code: the platforms are specific about what each one means, and the table further down decodes them.

Why does one bad message disable the whole thing?

Because a lot of self-hosted boards treat any failure as a permanent one. Fider's own bug report on this, issue 1404 from December 2025, reproduces it in four steps: create a webhook with a URL that will fail, save it, click test, find it disabled. Not rate limited, not retried. Disabled, and staying that way.

The maintainer's own audit in that thread says the webhook code "is pretty basic" with not much "in the way of tracking, retry mechanism", and lists what is missing: no delivery history, no timestamp of the last successful delivery, no count of successes against failures, no retry, no queue. That combination is what makes the failure invisible. There is no screen anywhere that would show you a webhook has not fired since a Tuesday in March.

This is not unique to one project. Canny.io's own public board carries an open request to store failed webhooks and add retry logic, which tells you the gap exists at the hosted end of the market too.

What was the failing message, though?

Often something small enough to feel unfair. In that same Fider thread the reporter tracked it down: a customer had used a double quote character in a request title, the board interpolated it straight into a JSON template, the JSON became invalid, and Slack returned a 400. One badly punctuated feature request took the integration offline.

The fix there was the template's own escape function, and the general principle holds whatever tool you are on: anything you interpolate into a JSON payload has to be JSON-escaped, not just pasted. You can see what the difference looks like:

printf '%s' 'He said "no"' \
  | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))'

The output is the quoted, escaped string your payload should contain. Without the escaping the quote closes the field early and the whole body is malformed.

The fix, in order

1. Test the webhook URL directly with the curl above. A 200 from Slack, or a 200 with wait=true from Discord, means the endpoint is alive and the problem is on the board's side.

2. Re-enable the integration in your board's settings. On Fider, the merged pull request 1438, from January 2026, added an environment variable, WEBHOOK_DISABLE_ON_FAILURE, so a failing webhook can be kept enabled instead of switched off on the first error. Upgrade if you are on an older build.

3. Escape every value you interpolate into the payload template. If your tool offers an escape helper, use it on every field, not only the ones that look risky.

4. If the direct call failed, generate a new webhook URL at the platform end and paste it in. A URL that has been committed to a repository is likely already dead, because Slack says it "actively searches out and revokes leaked secrets".

5. Fire a real event, not just a test button, and confirm it lands.

Your own support centre, in your own repository

One payment, no subscription, unlimited products.

What does the status code mean?

Slack publishes its own list, returning an HTTP status alongside a short string, and each one points somewhere different. Discord's are thinner, but the query parameter above changes what you can learn.

ResponsePlatformWhat it means
invalid_payloadSlackMalformed JSON, usually an unescaped quote. Do not retry without fixing it
no_active_hooksSlackThe incoming webhook is disabled
no_serviceSlackDisabled, removed, or invalid. Usually the app was uninstalled
team_disabledSlackThe workspace itself is no longer active
204 No ContentDiscordAccepted, but with wait off this is not proof it was posted
401, 403 or 404DiscordThe webhook was deleted, or the URL is wrong
429DiscordRate limited. The body carries retry_after in seconds

The trap: Discord tells you nothing by default

This is the detail that turns a five-minute problem into a five-week one. Discord's Execute Webhook endpoint takes a wait parameter, and its default is off:

when false a message that is not saved does not return an error

Discord, Execute Webhook

So a board that fires and forgets gets a success back for a message that was never posted. Adding ?wait=true to the URL makes Discord confirm the send and return the created message, which is the difference between an integration you can monitor and one you cannot. Discord separately restricts an IP that makes too many invalid requests, currently 10,000 in any 10 minutes, counting 401, 403 and 429 responses, so a badly behaved retry loop can take out more than the one webhook.

How do you make silence detectable?

You are relying on the absence of a message to mean something, and it never can. Two habits fix that cheaply.

Send a heartbeat. A scheduled job that posts one line to the same channel on a fixed day proves the whole path is alive, and its absence is something a person will actually notice, because they were expecting it.

Check the source of truth on a schedule instead of the notification. Whatever holds your requests, look at it directly once a week rather than trusting that alerts would have told you. Notifications are a convenience layer, and treating them as the record is what turns one failed delivery into a month of ignored customers.

Where Docket sits on this

Slack and Discord alerts are part of Docket's paid tiers, so this is our failure mode too and worth saying plainly rather than skipping. Docket is a static site with no queue of its own, so there is no server holding undelivered messages, and the durable record is the request itself sitting in your repository rather than the notification about it. That does not make a webhook call succeed. It means the thing you would lose in an outage is the ping, not the report, and you can go and read what arrived while the channel was quiet.

If you are choosing how much of your support process to hang off notifications, what breaks when you self-host is the wider version of this question.

Frequently asked questions

How do I know how long it has been broken?

On most self-hosted boards you cannot, because there is no delivery history and no last-success timestamp to read. The nearest thing is to compare the items on your board against the messages in the channel and find the first one with no matching post. That is also the strongest argument for a heartbeat, which gives you a dated last-known-good.

Should I just retry failed deliveries automatically?

Retry the ones worth retrying. A 429 from Discord tells you exactly how long to wait in its retry_after field, and a timeout is worth another attempt. A malformed payload is not: Slack's own guidance on invalid_payload is that the request "should not be retried without correction", because it will fail identically forever.

My webhook URL leaked into a public repository. Is it still valid?

Assume not. Slack states that it actively searches out and revokes leaked secrets, so a URL committed to a public repository may already be dead. Generate a new one, paste it in, and remember that removing the commit does not remove it from the history.

Does a private channel need anything different?

The webhook has to be installed into the specific channel it posts to, so moving a channel to private, or deleting and recreating it, can break an integration that was working. That failure usually surfaces as a 404 on Discord or no_service on Slack rather than as anything on your board.

Can I test without spamming the channel?

Create a throwaway channel, install a second webhook into it, and point your test at that. It keeps the noise out of the channel people read, and you can run the check as often as you like while debugging.