SDK & Integrations
Gotcha doesn’t have its own wire protocol for sending data — it accepts events and transactions via the Sentry ingestion protocol, and metrics and profiles via OTLP and pprof respectively. That means connecting your application doesn’t require any special “Gotcha SDK”: you install the official Sentry SDK for your language and point it at your Gotcha project’s DSN. From there the SDK behaves exactly as documented upstream — Gotcha just happens to be on the other end of the DSN instead of sentry.io.
Below: install and minimal init for PHP (including Laravel), JavaScript/Node, JavaScript in the browser, Python, and Go, followed by environment & release, sending performance data (tracing), pointers to metrics and profiling ingest, and a troubleshooting section for connection issues.
Where to find your DSN
Your project’s DSN lives on the “Setup” page: right after creating a project you’re automatically redirected there (a URL like /projects/<id>/setup), and you can get back to it later via the “Setup” button in the projects list or the “DSN keys” section on the “Project settings” page. A DSN looks like this:
https://<PUBLIC_KEY>@<your-gotcha-host>/<PROJECT_ID>
In every example below, replace <YOUR_PROJECT_DSN> with this full string.
Which DSN goes where. A project doesn’t have a single DSN — it has several, one per key, and keys differ by type. For JavaScript in the browser, use the browser key’s DSN; for PHP, server-side Node, Python, and Go, use the server key’s DSN. See Ingest keys for the full list of what each type is allowed to send and why it matters.
PHP (plain project)
composer require sentry/sentry
Repository: https://github.com/getsentry/sentry-php
<?php
require __DIR__ . '/vendor/autoload.php';
\Sentry\init([
'dsn' => '<YOUR_PROJECT_DSN>',
'environment' => getenv('APP_ENV') ?: 'production',
'traces_sample_rate' => 0.2, // enables performance tracing; see the section below
]);
Send a test error — either just let an unhandled exception happen (the SDK hooks set_exception_handler automatically), or capture one explicitly:
try {
throw new \RuntimeException('Gotcha test error');
} catch (\Throwable $e) {
\Sentry\captureException($e);
}
The event shows up under your project’s “Issues” within a few seconds.
PHP (Laravel)
composer require sentry/sentry-laravel
Repository: https://github.com/getsentry/sentry-laravel
Publish the config and set the DSN in one go (this adds the variable to .env and creates config/sentry.php):
php artisan sentry:publish --dsn=<YOUR_PROJECT_DSN>
Or add it to .env manually:
SENTRY_LARAVEL_DSN=<YOUR_PROJECT_DSN>
SENTRY_TRACES_SAMPLE_RATE=0.2
SENTRY_ENVIRONMENT=production
The package registers itself into Laravel’s exception handler automatically — no extra init call is needed. Send a test event:
php artisan sentry:test
It shows up under “Issues” in the project whose DSN you configured.
PHP (Symfony)
composer require sentry/sentry-symfony
Repository: https://github.com/getsentry/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 the DSN (empty = SDK off), and enable tracing via env — config/packages/sentry.yaml:
sentry:
dsn: '%env(SENTRY_DSN)%'
options:
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],
Variables in .env:
SENTRY_DSN=<YOUR_DSN>
SENTRY_TRACES_SAMPLE_RATE=0.2
The bundle captures unhandled exceptions on its own. To test — \Sentry\captureMessage('check') or a temporary route that throws; the event shows up under Issues.
ignore_exceptionsmatters: 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.
CMS: WordPress and Joomla
Sites running on a CMS need no code at all — there are ready-made extensions that install through the regular extension manager and take a single DSN field. Inside they use the same official Sentry SDK as the examples above, plus the browser SDK for Web Vitals.
| CMS | Extension | What it collects |
|---|---|---|
| WordPress 5.9+ | gotcha-monitoring | PHP errors (fatals included), transactions per page type (single.post, archive.category, rest:/wp/v2/posts, wp-cron), JS errors and Web Vitals |
| Joomla 4.2+, 5, 6 | pkg_gotcha | PHP errors, transactions per component (com_content.article), JS errors and Web Vitals |
| 1C-Bitrix (Site Management) | gotcha.monitoring | PHP errors (via the core’s own ExceptionHandlerLog), transactions per script (/catalog/index.php), JS errors and Web Vitals |
| Drupal 10, 11 | gotcha_monitoring | PHP errors (via the stock logger channel), transactions by route name (entity.node.canonical), JS errors and Web Vitals |
| OpenCart 4.x | gotcha (ocmod) | PHP errors (handler chain), transactions by route (product/product), JS errors and Web Vitals |
| MODX Revolution 3 | gotcha (transport) | PHP errors (handler chain), transactions by template (web:BaseTemplate), JS errors and Web Vitals |
Installation is the same either way: install the archive through the extension manager, enable it, paste the DSN from the project “Setup” page. While the DSN is empty the extension does nothing — not a single outbound request.
A walk-through of how the extensions are built, including transaction naming and scoping composer dependencies: WordPress, Joomla, 1C-Bitrix, Drupal, OpenCart, MODX.
On SaaS site builders (Tilda and the like) there is nowhere to install an extension — the server is not yours. The browser half is still available: JavaScript errors and Web Vitals go in as a block in <head>, while uptime and SSL are checked from the outside and need no code at all. More: monitoring a Tilda site.
JavaScript / Node.js (server)
npm install @sentry/node
Repository: https://github.com/getsentry/sentry-javascript
const Sentry = require("@sentry/node");
// or: import * as Sentry from "@sentry/node";
Sentry.init({
dsn: "<YOUR_PROJECT_DSN>",
environment: process.env.NODE_ENV || "production",
tracesSampleRate: 0.2,
});
Test error:
try {
throw new Error("Gotcha test error");
} catch (e) {
Sentry.captureException(e);
}
Before a short-lived process exits (a CLI script, a serverless function, a worker that returns immediately), make sure to flush the buffer:
await Sentry.close(2000); // wait up to 2s for the event to be sent
JavaScript (browser)
npm install @sentry/browser
import * as Sentry from "@sentry/browser";
Sentry.init({
dsn: "<YOUR_PROJECT_DSN>",
environment: "production",
tracesSampleRate: 0.2, // tracing + automatic Web Vitals collection (LCP/INP/CLS/FCP/TTFB)
});
Test error — any unhandled exception on the page is captured automatically; explicitly:
Sentry.captureException(new Error("Gotcha test error"));
If your site sets a Content-Security-Policy with connect-src, add your Gotcha instance’s address to it — otherwise the browser will silently block the request to your DSN.
The browser sends events to the address in the DSN — often a different domain than the site itself (site on app.example.com, Gotcha on gotcha.example.com). Gotcha’s ingest replies with CORS headers and handles the preflight (OPTIONS), so the browser SDK sends directly, with no proxy or tunnel. The public key in the DSN is public by design — the receiver allows any origin.
Python
pip install sentry-sdk
Repository: https://github.com/getsentry/sentry-python
import sentry_sdk
sentry_sdk.init(
dsn="<YOUR_PROJECT_DSN>",
environment="production",
traces_sample_rate=0.2,
)
Test error:
try:
raise RuntimeError("Gotcha test error")
except Exception:
sentry_sdk.capture_exception()
For Django/Flask/FastAPI and other frameworks, sentry-sdk auto-enables the matching integration when it detects the framework is installed — no separate opt-in is needed beyond calling sentry_sdk.init(...) at your app’s entry point.
Go
go get github.com/getsentry/sentry-go
Repository: https://github.com/getsentry/sentry-go
package main
import (
"errors"
"time"
"github.com/getsentry/sentry-go"
)
func main() {
err := sentry.Init(sentry.ClientOptions{
Dsn: "<YOUR_PROJECT_DSN>",
Environment: "production",
TracesSampleRate: 0.2,
})
if err != nil {
panic(err)
}
// REQUIRED: without Flush the process can exit before the event
// buffer has actually been sent over the network.
defer sentry.Flush(2 * time.Second)
sentry.CaptureException(errors.New("Gotcha test error"))
}
Environment & release
environment and release are two fields worth setting from day one: they get attached to every event, transaction, and metric, and are used for filtering across nearly every section of Gotcha.
\Sentry\init([
'dsn' => '<YOUR_PROJECT_DSN>',
'environment' => 'staging',
'release' => 'my-app@' . trim(shell_exec('git rev-parse --short HEAD')),
]);
That way production errors don’t drown among your staging environment’s noise, and a performance regression can be traced back to the exact release it started in.
Performance & tracing (transactions)
The traces_sample_rate option (tracesSampleRate in JS) turns on transactions — the unit of performance data that powers the Performance section. Its value is the fraction of requests to trace: 1.0 traces everything, 0.2 traces one in five, and 0 (the default) disables tracing entirely, sending only errors.
On top of that, “Project Settings → Performance” has a server-side sampling knob (sample_rate, 0..1) that’s applied on top of whatever the SDK already sent — useful for trimming stored transaction volume without redeploying your app. In the browser, enabling tracing also automatically collects Web Vitals (LCP/INP/CLS/FCP/TTFB) — no separate opt-in needed.
Metrics (OTLP)
Numeric metrics (counter/gauge/histogram) are accepted by Gotcha over the OpenTelemetry protocol (OTLP/HTTP), not through the Sentry SDK. Point your application’s OTLP exporter (or an OpenTelemetry Collector) at:
POST https://<your-gotcha-host>/v1/metrics
Authorization: Bearer <PUBLIC_KEY>
<PUBLIC_KEY> is the public key part of your project’s DSN (the segment between https:// and @), not the full DSN. Most OTel exporters have a built-in way to set headers (headers: in a Collector config, OTEL_EXPORTER_OTLP_METRICS_HEADERS as an env var). More on metric types, aggregations, and threshold alerts: Metrics.
Traces (OTLP)
Gotcha accepts transactions and spans over OpenTelemetry as well as over the Sentry protocol — the same way it accepts metrics:
POST https://<gotcha_address>/v1/traces
Authorization: Bearer <PUBLIC_KEY>
This is the path for teams whose tracing already runs on OpenTelemetry: there is no need to swap instrumentation for a Sentry SDK, just point the existing exporter (or collector) at this address. The spans land in the same Transactions and Endpoints views as the ones sent by a Sentry SDK.
Worth knowing:
- the transaction name comes from the root span’s name — the usual cardinality rules apply (an identifier in the name turns one endpoint into millions, see Cardinality);
- span nesting is preserved, and the waterfall shows the same structure as your trace;
- profiles are not linked to OTLP traces automatically: the link is built on
trace_id, so pprof must be sent with the same value.
Profiling (pprof)
Besides the profiles a Sentry SDK sends alongside traces (profiles_sample_rate in the Python/PHP/JS/Go SDKs), Gotcha also accepts raw pprof profiles directly:
POST https://<your-gotcha-host>/api/v1/profiles/pprof?service=<service-name>&environment=<environment>
Authorization: Bearer <PUBLIC_KEY>
Content-Type: application/octet-stream
The body is a regular gzip-compressed pprof profile (e.g. the output of go tool pprof or runtime/pprof). More on flame graphs, in-app vs. system frames, and regressions: Profiling.
Not seeing an event?
If an error you sent hasn’t shown up under “Issues” after a reasonable wait, check these in order:
| Cause | How to check |
|---|---|
| Wrong or revoked DSN | Compare it against what’s shown under “Project Settings → DSN keys”; ingest returns 401/403 for an unknown or revoked public key. Reissuing a key immediately invalidates the old DSN. |
| DSN from a different project | The project_id in the DSN must match the project the key actually belongs to — otherwise ingest returns 403 sentry_key does not match project. |
| Network/firewall | Your application needs HTTPS/HTTP reachability to your Gotcha instance’s address (GOTCHA_BASE_URL) — try curl -i <your-gotcha-host>/readyz from the same machine/container the app runs on. A corporate proxy or egress firewall can silently drop outbound requests. |
| Event/body too large | By default ingest caps request bodies at 1 MB (GOTCHA_MAX_EVENT_BYTES on the instance); exceeding it returns 413. This can bite events with very long stack traces or large breadcrumb trails. |
| Organization quota exhausted | Ingest returns 429 with Retry-After once the monthly quota is used up; check “Organization settings → Usage & rate limits”. The oss edition defaults to unlimited (0), but the instance admin may have set a cap. |
| Per-DSN rate limit hit | The second source of 429: each project is capped at GOTCHA_INGEST_RATE_PER_SEC requests per second (500 by default, burst 2×). Unlike the quota, the Retry-After here is about a second — SDKs with built-in backoff recover on their own. A sustained hit usually means an event loop is misfiring (an error per request, a retry storm). |
| SDK didn’t flush in time | In short-lived processes (CLI scripts, serverless functions, workers that exit immediately), call Flush/close before exiting — see the Go and Node examples above; without it, the event buffer may never actually be sent. |
| CSP blocking the request in the browser | If the page sets a Content-Security-Policy, add your Gotcha host to connect-src. |
If none of these explain it, turn on the SDK’s debug mode (debug: true on most Sentry SDKs) and see what it logs when it tries to send — that almost always pinpoints the exact cause.