watchtower-laravel maintained by phattarachai
Watchtower Laravel
Laravel client for Watchtower, a self-hosted Sentry-compatible exception tracker. Installs and configures sentry/sentry-laravel, wires Integration::handles($exceptions) into bootstrap/app.php, and exposes a same-origin browser tunnel (/api/watchtower-relay) that proxies envelopes to your Watchtower instance — dodging ad-blockers that strip ?sentry_key= query strings.
Install
composer require phattarachai/watchtower-laravel
php artisan watchtower:install --dsn=http://your-public-key@your-watchtower-host/42
The install command:
- Validates and writes
WATCHTOWER_DSNandSENTRY_LARAVEL_DSNto.env. - Patches
bootstrap/app.phpto callSentry\Laravel\Integration::handles($exceptions)insidewithExceptions(...). - Publishes
config/watchtower.php. - If
vite.config.{js,ts}is present, writesVITE_SENTRY_DSN,VITE_SENTRY_TUNNEL, andVITE_SENTRY_ENVIRONMENTand prints a Vite entry snippet.
Re-running is idempotent. Pass --dry-run to preview changes.
Configuration
| Env key | Default | Purpose |
|---|---|---|
WATCHTOWER_DSN |
falls back to SENTRY_LARAVEL_DSN |
Watchtower DSN: http://{key}@{host}/{numeric-project-id}. |
WATCHTOWER_RELAY_ENABLED |
true |
Register the relay route on boot. |
WATCHTOWER_RELAY_PATH |
/api/watchtower-relay |
Relay endpoint path (must live under /api/). |
WATCHTOWER_RELAY_TIMEOUT |
5 |
Upstream request timeout (seconds). |
WATCHTOWER_RELAY_ASYNC |
false |
Forward envelopes through a queued job instead of sync. |
WATCHTOWER_RELAY_QUEUE |
(default queue) | Queue name when async is enabled. |
WATCHTOWER_VERIFY_SSL |
true |
Verify upstream TLS certificate. |
WATCHTOWER_CONNECT_TIMEOUT |
3 |
Guzzle connect timeout (seconds). |
Browser side
The browser SDK posts envelopes to your own app at /api/watchtower-relay. The relay parses your configured DSN, forwards the request body verbatim to {scheme}://{host_with_port}/api/watchtower-relay on the Watchtower instance, and passes back the upstream status and rate-limit headers.
import * as Sentry from '@sentry/browser';
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
tunnel: import.meta.env.VITE_SENTRY_TUNNEL,
environment: import.meta.env.VITE_SENTRY_ENVIRONMENT,
});
Because the request hits your own origin under /api/, ad-blockers don't recognize it as Sentry traffic.
Async forwarding
Set WATCHTOWER_RELAY_ASYNC=true to dispatch each forward through a ForwardEnvelope job. The relay returns 202 {"queued": true} immediately and the worker performs the upstream POST. Failures are logged but not retried beyond Guzzle's default behavior — the Sentry SDK retransmits anyway.
Verify
php artisan watchtower:test
Prints the resolved config, runs sentry:test, and POSTs a synthetic envelope through the relay path.
Troubleshooting
See the upstream Watchtower skill: https://watchtower.phattarachai.app/skill/watchtower-error-tracking.
License
MIT.