Appearance
Error Tracking
kendo collects exceptions from your applications, fingerprints each one, and deduplicates identical errors into a single error group on your project — with an occurrence count and first/last-seen timestamps. This guide wires an application up to report into kendo end to end: mint a token, send errors, and triage them.
For Laravel applications, use the canonical client library, kendo-error-tracker. For any other language or framework, post raw HTTP to the Error Events API.
Requires the Error Tracking feature
Error ingestion is only available when the Error Tracking feature is enabled for your workspace. If it is disabled, requests return 403 with "feature_restricted": true.
Why a client library, not raw HTTP?
The package is more than convenience — it enforces a contract a hand-rolled POST can't:
- Source-side PII scrubbing. Messages and stack traces are scrubbed before they leave your server. JWTs,
Bearertokens, BSNs (Dutch citizen service numbers), and email addresses are each replaced with a[REDACTED:<kind>]marker. Scrubbing has to happen at the source and consistently — a rawPOSTships whatever the exception happened to carry. - Exact path normalization. Each stack frame's absolute path has your app's own
base_path()stripped, so the same exception thrown from/var/www/html/app/Foo.phpand/home/forge/app/Foo.phpboth arrive asapp/Foo.phpand fingerprint into one group. - Async, swallow-on-failure delivery. Reports dispatch to the queue with 0 retries; a failed send logs locally and is never requeued.
report()never throws and never blocks the request that hit the error — error tracking can't become the thing that slows down or breaks your app.
This is why allied projects should not POST raw HTTP when a client exists: the scrubbing contract would drift, and PII could leak server-to-server. The raw endpoint remains available for languages the client doesn't cover — see Raw HTTP below.
1. Mint a project token
Error ingestion authenticates with a Read & write project token:
- Open the project and go to Settings (
/projects/{projectId}/settings). - Scroll to the API Tokens section.
- Click Create Token, name it (e.g.
Production error reporter), and set the Access to Read & write. - Click Create and copy the token — it is shown only once.
The token is bound to that one project. Used against a different project's route, the server rejects it (403); the client swallows that like any other failure.
2. Install the client (Laravel)
bash
composer require script-development/kendo-error-trackerThe ErrorTrackerServiceProvider is auto-discovered through Laravel package discovery — no manual registration. Publish the config if you want to tune it:
bash
php artisan vendor:publish --tag=error-tracker-configConfigure
Set the environment variables (the config reads ERROR_TRACKER_*):
dotenv
ERROR_TRACKER_KENDO_URL=https://script.kendo.dev
ERROR_TRACKER_PROJECT=7
ERROR_TRACKER_TOKEN=your-project-token
ERROR_TRACKER_ENVIRONMENT=production
ERROR_TRACKER_RELEASE=v1.2.3| Env var | Config key | Description | Default |
|---|---|---|---|
ERROR_TRACKER_KENDO_URL | kendo_url | Base URL of your kendo tenant — https://{tenant}.kendo.dev | — |
ERROR_TRACKER_PROJECT | project | The kendo project id that owns the errors (the {project} route key — kendo binds it by id) | — |
ERROR_TRACKER_TOKEN | token | A Read & write project token (sent as a Bearer token) | — |
ERROR_TRACKER_ENVIRONMENT | environment | Deploy environment label sent with each event | APP_ENV |
ERROR_TRACKER_RELEASE | release | Optional release identifier (git sha / version tag) | null |
ERROR_TRACKER_SYNC | sync | false queues the report; true POSTs inline | false |
ERROR_TRACKER_CONNECT_TIMEOUT | connect_timeout | Seconds to wait while connecting to the kendo host | 2 |
ERROR_TRACKER_TIMEOUT | timeout | Total seconds to wait for the POST | 5 |
ERROR_TRACKER_PROJECT is the numeric project id, not its code — it is the {project} segment in {kendo_url}/api/projects/{project}/error-events.
Report exceptions
Report from your application's exception handler. In Laravel 11+ (bootstrap/app.php):
php
use ScriptDevelopment\KendoErrorTracker\ErrorTracker;
use Throwable;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->report(function (Throwable $e): void {
app(ErrorTracker::class)->report($e);
});
})Or from a classic App\Exceptions\Handler::report():
php
public function report(Throwable $e): void
{
app(\ScriptDevelopment\KendoErrorTracker\ErrorTracker::class)->report($e);
parent::report($e);
}That single call is the whole integration. report() is swallow-on-failure: it never throws and never blocks the request, so it is safe to call from inside your own exception handler.
Dispatch modes
- Async (default).
report()dispatches a queued job (0 retries) — a failed POST logs to the localerror_logand is never requeued, so error tracking never amplifies load during an outage. - Sync. Set
ERROR_TRACKER_SYNC=trueto POST inline in the request that reported. Both modes swallow every failure.
Multi-tenant apps
In an app with one database per tenant, user ids restart in each tenant, so user 42 of two tenants would send one user hash and count as one user. From kendo-error-tracker v0.2.1, register a resolver that returns the current tenant key, once, in a service provider's boot():
php
app(ErrorTracker::class)->scopeUserHashUsing(fn (): int|string|null => tenant()?->getTenantKey());- The tenant key goes into the hash's key and is never sent, so the same user id in two tenants gives two hashes.
- The resolver runs when a report is made. Return the tenant your app already resolved for the request; do not look it up in a database or a cache, because that lookup can be what failed.
- When it returns
null(no tenant), the hash is the same as without a resolver. When it throws, or returns anything but an integer or a non-empty string, no hash is sent; the report still is.
Raw HTTP (non-Laravel)
For systems the client doesn't cover, post directly to the Error Events API. You are then responsible for scrubbing PII and normalizing paths yourself — the server applies a best-effort path-prefix fallback, but it can't scrub message contents or know your deploy root the way the client does.
bash
curl -X POST https://{tenant}.kendo.dev/api/projects/1/error-events \
-H "Authorization: Bearer your-project-token" \
-H "Content-Type: application/json" \
-d '{
"environment": "production",
"release": "v1.4.2",
"exception_class": "RuntimeException",
"message": "Undefined array key \"user_id\"",
"stack_trace": "#0 /var/www/app/Http/Controllers/OrderController.php(42): handle()\n#1 {main}"
}'A successful ingest returns 202 Accepted with an empty body. See the Error Events API for the full field reference, validation limits, and error codes.
What gets sent
Both paths POST the same body to {kendo_url}/api/projects/{project}/error-events:
json
{
"environment": "production",
"release": "v1.2.3",
"exception_class": "RuntimeException",
"message": "<scrubbed exception message>",
"stack_trace": "<scrubbed, path-normalized stack trace>"
}release is omitted when unset. kendo-error-tracker v0.2 also sends each optional field it can read: the cause chain, the runtime, the framework, memory use, the context, the host and, for a request with a signed-in user, a keyed user hash (see Request Fields). Any other fields in the body are ignored — kendo deliberately does not persist request bodies or arbitrary context with error groups.
Where errors land — and triage
Ingested events become error groups on the project. Identical errors (same exception class and normalized top stack frame) collapse into one group with an occurrence count, so a fault firing thousands of times shows up once. Each group tracks first_seen_at, last_seen_at, and the latest message and stack trace.
An event may also name the host that ran it and carry a keyed user hash: the HMAC-SHA256 of the signed-in user's id, keyed with a secret only your app holds. A group then counts its events per host and how many distinct users it hit. The hash takes the place of the user id: kendo cannot turn it back into one, and its API returns only the number of distinct hashes, never a hash. A value that is not a well-formed hash (a raw id, an email address) is dropped, and the event is still stored. The hash covers that one field: the message and the stack trace are stored as sent, which is why they are scrubbed before they leave your server (see Raw HTTP for a raw POST). See Affected users.
Error groups are operational data and are pruned automatically: a group is deleted once its last_seen_at is older than the retention window (default 90 days). Active errors that keep occurring are never pruned. A group's user hashes and hosts each expire once they have not been seen for the length of that window, even while the group keeps firing, so a user who has not hit the error within the window no longer counts.
Create an issue from an error
Once you can see error groups in kendo, you can turn any of them into a tracked Bug issue in one click — no copy-pasting needed.
On the error group detail page, click Create issue next to the "Stack trace" heading. kendo creates a Bug issue pre-filled with:
- Title:
{ExceptionClass}: {latest message}(truncated to 255 characters) - Description: stack trace, occurrence count, environment, first/last seen timestamps, and a back-link to the error group
After the issue is created, the button is replaced by a "Tracked in KD-XXXX →" link that navigates directly to the issue. The issue page shows a corresponding "Originated from error" section linking back to the group.
Status and tracking are independent. Creating a Bug issue does not change the error group's status — an Open error stays Open until you explicitly resolve it. You can resolve, ignore, or reopen an error group regardless of whether a linked issue exists.
Already linked? If an error group already has a linked issue, the "Create issue" button is replaced by the tracked-issue link — you cannot create a second issue from the same group.
The underlying API endpoint is POST /api/projects/{projectId}/error-groups/{errorGroupId}/promote — see the Error Groups API for the full reference.
See Also
- Error Events API — Endpoint reference, fields, deduplication, and error codes
- Error Groups API — Status lifecycle and promote endpoint reference
- Project Tokens — Create and manage project tokens
- Sending Reports — Submit user-facing feedback reports the same way
kendo-error-tracker— Client library source and changelog