Connecting Gotcha to Symfony: separate backend and frontend
Gotcha ingests data over the Sentry ingest protocol and over OTLP, so integration means official Sentry SDKs pointed at your instance. Nothing Gotcha-specific ends up in your application: the same SDK you would use with any Sentry-compatible receiver, just with the DSN aimed at your own server.
Let’s cover a practical case rather than a “hello world” one: an app made of two parts — a Symfony API backend and a separate frontend (SPA or classic JS) that live on different domains and talk over an API. That layout adds one wrinkle — cross-origin sending from the browser — which we’ll cover separately.
What we’ll connect
| Signal | Sent from | What you need |
|---|---|---|
| Errors (exceptions) | Symfony backend | sentry/sentry-symfony bundle |
| Tracing (transactions, spans) | Symfony backend | same bundle, traces_sample_rate |
| Web Vitals, front-end errors | browser | @sentry/browser |
| Metrics | backend (OTLP) | HTTP POST to /v1/metrics |
| Uptime | Gotcha, outbound | HTTP monitor, no code |
| Alerts | Gotcha | a channel (Telegram/webhook/email) in the UI |
Errors and tracing from the backend, Web Vitals from the frontend, uptime from the outside. Three independent sources, all sending to the same Gotcha project by its DSN.
Where the DSN comes from
After you create a project, Gotcha takes you to the Connect page
(/projects/<id>/setup); you can return via the “Connect SDK” button. The DSN
looks like this:
https://<public_key>@gotcha.example.com/<project_id>
Both the backend and the browser use the same DSN — the public_key in it is
public by design. Keep it in an environment variable: moving to another instance
(say, from local to a VPS) becomes a one-line change.
Backend: Symfony
composer require sentry/sentry-symfony
The default Flex recipe enables the bundle only in prod and without tracing.
For a self-hosted setup it’s handier to keep it active in every environment but
gated on SENTRY_DSN (empty = SDK off, a full no-op), and enable tracing via
env. config/packages/sentry.yaml:
sentry:
dsn: '%env(SENTRY_DSN)%'
options:
# A non-zero sample rate turns on performance tracing.
traces_sample_rate: '%env(float:SENTRY_TRACES_SAMPLE_RATE)%'
environment: '%kernel.environment%'
# 404/405 are ordinary web noise (scanners, dead links), not app errors.
ignore_exceptions:
- 'Symfony\Component\HttpKernel\Exception\NotFoundHttpException'
- 'Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException'
Register the bundle in every environment (config/bundles.php):
Sentry\SentryBundle\SentryBundle::class => ['all' => true],
And set the variables (.env / production environment):
SENTRY_DSN=https://<public_key>@gotcha.example.com/<project_id>
SENTRY_TRACES_SAMPLE_RATE=0.2 # errors always send; traces on 20% of requests
From there the bundle captures unhandled exceptions on its own. To verify, use a
temporary route that throws, or \Sentry\captureMessage('check') — the event
shows up under “Issues”.
About
ignore_exceptions. Without it,NotFoundHttpExceptionflows into Gotcha as an error, and every scan or dead link clutters your issues. Ignoring client 404/405 leaves only real application errors in the stream.
Frontend: a separate domain and cross-origin sending
The frontend is a separate app on its own domain (say app.example.com), and
Gotcha is on gotcha.example.com. The browser SDK will send telemetry to a
different origin — the key difference from the backend, which sends
server-to-server with no CORS involved.
If the frontend has no bundler (classic JS), build a self-contained
@sentry/browser bundle with any bundler, e.g. esbuild:
npm i @sentry/browser
npx esbuild <(echo "export * from '@sentry/browser'") \
--bundle --minify --format=iife --global-name=Sentry \
--outfile=public/js/sentry.min.js
Then initialize it (in <head> or at the start of the page scripts):
<script src="/js/sentry.min.js"></script>
<script>
Sentry.init({
dsn: "https://<public_key>@gotcha.example.com/<project_id>",
integrations: [Sentry.browserTracingIntegration()],
tracesSampleRate: 0.2,
});
</script>
browserTracingIntegration collects Web Vitals (LCP, CLS, INP, FCP, TTFB) on
its own and attaches them to the page-load transaction.
About CORS. The browser on app.example.com POSTs to gotcha.example.com —
a cross-origin request. Gotcha’s ingest replies with CORS headers and handles the
preflight (OPTIONS), so the browser SDK sends directly, no proxy needed. The
DSN (public key) is public, so the receiver allows any origin — just like the
Sentry cloud receiver.
If your receiver isn’t reachable by the browser directly for some reason (a strict network perimeter, blocking), the Sentry SDK can send via a tunnel — POST to your own domain, and the backend forwards it to Gotcha. For a typical setup you don’t need it: direct sending is simpler and doesn’t load the backend.
Metrics (optional)
Business metrics go over OTLP: POST to /v1/metrics with the header
Authorization: Bearer <public_key> and an OTLP JSON body. From Symfony that’s a
one-off console command (schedulable via cron) or any HTTP client. See the
metrics documentation.
Uptime: not a single line in the app
Uptime needs no code changes — Gotcha hits the public URL from the outside. In the project: Uptime → New monitor → HTTP, your site’s URL, an interval and fail/recovery thresholds. Incidents, SSL alerts and a public status page come out of the box. See Uptime.
Alerts
Under Alerts, the rules (new issue, regression, spike) are already on; you just add a delivery channel — Telegram, webhook or email (the last needs SMTP). Channels attach to uptime monitors too, so a site outage reaches your notification as well. See Alerts.
Moving to production — one variable
That’s the whole point of env-driven config: both the backend and the browser
read the DSN from the environment. Stand up Gotcha on a VPS with a real domain —
change SENTRY_DSN (and, for the frontend, the string in Sentry.init), nothing
else. The application code stays untouched.
Summary
- Symfony backend →
sentry/sentry-symfony, DSN and sample rate via env, ignore 404/405. - Frontend (separate domain) →
@sentry/browser, sends directly — Gotcha’s ingest returns CORS. - Uptime — a monitor on the public URL, no code.
- Alerts — a channel in the UI, attached to rules and monitors.
- Migration — change
SENTRY_DSN, code untouched.
Next: the documentation, installation and the SDK connection section.