AlphornAlphorn Docs

Troubleshooting

Troubleshoot Alphorn - notifications not arriving, delivery statuses, webhook HTTP errors (400, 401, 403, 404, 413, 429, 508), self-hosting, SSO, and SSE streaming issues.

Notifications not arriving

Check the delivery logs

The most common starting point. Go to Messages in the dashboard and open the notification. It shows the delivery status for every channel.

  • Pending / Processing - Still being sent. Wait a moment and refresh.
  • Delivered - Alphorn sent it successfully. The issue is on the channel side (check Slack/Discord/Email).
  • Failed - The last attempt failed. Alphorn retries automatically while attempts remain; check the error message for details.
  • Stale - No worker picked up the delivery in time. On a self-hosted instance, check that the worker is running (see Architecture & Scaling).

If a message has no deliveries at all, no channel matched: either no channel is linked to the webhook, the channels or their link to the webhook are switched off, or the filters excluded it. See Retries for how retries and the 24-hour delivery window work.

Verify your webhook URL

Make sure your webhook URL is correct and accessible:

curl -s -X POST https://app.alphorn.dev/n/V1StGXR8_Z5jdHi6B-myT \
  -H "Authorization: Bearer $ALPHORN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Test", "message": "Testing webhook"}'

You should get back:

{"messageId": "0199b2f4-3c1e-7a8d-9f20-5d4c3b2a1e0f"}

If you get a 404, the URL is wrong. If you get a 401, the API key is missing or wrong. If you get no response, check your network connectivity. See API issues for all error codes.

Check filtering rules

If a channel isn't receiving notifications, it might be filtered out. Filters are set per webhook and channel: open the webhook, expand the channel in the channel list, and check its filter. All conditions in a group must match, but only one group has to. Use the filter's test panel to try a sample notification, or temporarily clear the filter to confirm.

Channel configuration issues

Common mistakes by channel type:

ChannelCommon issue
SlackWebhook URL expired or was regenerated - get a new one from Slack
DiscordWebhook URL deleted - recreate in Discord channel settings
Email (SMTP)Wrong port, missing TLS, or credentials rejected by provider
TelegramBot not added to chat, or wrong chat ID (group IDs are negative numbers)
PagerDutyWrong integration key - use the Events API v2 integration key, not the API key
Microsoft TeamsIncoming Webhook connector disabled or removed from channel

Self-hosting issues

Database connection errors

Symptom: Alphorn won't start, logs show connection errors.

Check your DATABASE_URL:

postgres://user:password@host:5432/dbname
  • Is PostgreSQL running and accepting connections?
  • Is the hostname correct? Inside Docker Compose, use the service name (e.g., db), not localhost.
  • Is the port correct? Default is 5432.
  • Do the credentials match your PostgreSQL configuration?

Migrations fail on startup

The Docker image applies migrations on every start; from source you run pnpm db:deploy. If they fail:

  • Check that the database user has permission to create tables and alter schema
  • permission denied to create extension "pg_trgm": let a superuser run CREATE EXTENSION pg_trgm; in the Alphorn database once, then restart
  • The container exits right away if PostgreSQL isn't reachable yet - make sure it starts after the database is ready (e.g. depends_on with condition: service_healthy)
  • Check PostgreSQL logs for specific error messages
  • Make sure you're running PostgreSQL 18+ (older versions fail with function uuidv7() does not exist)

Sign-in fails over plain HTTP

Alphorn uses secure cookies, which browsers only accept over HTTPS (or on localhost). Put Alphorn behind a reverse proxy with TLS and set BETTER_AUTH_URL to the https:// URL. See Self-Hosting → Reverse Proxy & HTTPS.

Alphorn starts but isn't accessible

  • Check that port 3000 is mapped correctly in your Docker Compose or firewall
  • If behind a reverse proxy, make sure the proxy passes requests to port 3000
  • Verify BETTER_AUTH_URL matches the URL you're accessing Alphorn from

SSO / OAuth not working

  • Verify the callback URL matches exactly: {BETTER_AUTH_URL}/api/auth/callback/{provider} for GitHub, Google, and Microsoft, or {BETTER_AUTH_URL}/api/auth/callback/{OIDC_PROVIDER_ID} for OIDC
  • Microsoft stopped working after an upgrade? The callback changed to /api/auth/callback/microsoft - see Self-Hosting → Microsoft
  • Check that BETTER_AUTH_URL includes the protocol (https://) and has no trailing slash
  • For OIDC providers, verify the discovery URL returns a valid configuration
  • Check provider-specific setup in Self-Hosting → SSO / OAuth

API issues

HTTP 400 - Invalid JSON body / JSON body must be an object

You sent Content-Type: application/json (or another JSON type), but:

  • Invalid JSON body - the body isn't valid JSON. Check for trailing commas and unquoted keys.
  • JSON body must be an object - the body is a JSON array or a plain value. Wrap it in an object, e.g. {"message": "..."}.

To send plain text, use Content-Type: text/plain.

HTTP 401 - Missing or invalid Authorization header / Invalid API key

The webhook requires an API key.

  • Missing or invalid Authorization header - the header is missing or doesn't start with Bearer (exactly like this, case-sensitive). Check that your API key variable isn't empty.
  • Invalid API key - the key is wrong, or it was regenerated. If you've lost the key, use Regenerate key on the webhook page - the full key is only shown once.

The key is checked before the webhook's enabled state, so a disabled webhook called with a wrong key also returns 401.

HTTP 403 - Webhook is disabled

The webhook was disabled in the dashboard, or automatically after a plan downgrade. Enable it on the webhook page.

HTTP 404 - Not found

The URL doesn't match any webhook. Check for typos. If the webhook was deleted, or its URL was regenerated, the old URL no longer works.

HTTP 413 - Payload too large

The request body is larger than 1 MiB. Self-hosted instances can raise the limit with WEBHOOK_MAX_BODY_BYTES.

HTTP 429 - Message quota exceeded

You've hit your plan's monthly message limit. Check your usage under Billing. Options:

  • Upgrade your plan
  • Reduce notification volume with more selective filtering
  • Wait for the next billing cycle

HTTP 508 - Loop detected

The notification was forwarded from one Alphorn webhook to another through Webhook channels too many times (3 hops by default), or it came back to a webhook it already passed. Check your Webhook channels for a forwarding loop.

Streaming issues

SSE connection drops

Server-Sent Events connections may drop due to network issues, proxy timeouts, or server restarts. Your client should reconnect automatically:

const eventSource = new EventSource(
  "https://app.alphorn.dev/api/stream/3f2a9c1e-7b4d-4e8a-9c2f-1d5e6b7a8c90"
);

eventSource.onerror = () => {
  // EventSource reconnects automatically
  // Implement backoff if you need custom reconnection logic
  console.log("Connection lost, reconnecting...");
};

Alphorn sends a keepalive comment every 30 seconds. If connections still drop frequently behind a reverse proxy, increase the proxy timeout:

# Nginx
proxy_read_timeout 86400;
proxy_buffering off;

Live Stream returns 503

"SSE runs in standalone mode; route /api/stream/* to the SSE server" means SSE_MODE=standalone is set, but your reverse proxy sends /api/stream/* to the web app. Route it to the SSE server's /stream/* instead - see Streaming → Self-Hosting.

Live Stream deliveries fail with MODE=worker

A separately running worker has no connected stream clients. Run the standalone SSE server and set SSE_MODE=standalone, SSE_SERVER_URL, and SSE_INTERNAL_SECRET on the web app and the worker.

Still stuck?

  • Check the FAQ for common questions
  • Open an issue on GitHub

On this page