An incident is a technical or business problem that needs an administrator's attention.
Examples are an unknown billing subscription, an invalid order status transition, a failed external integration, or inventory below a critical threshold.
An incident is not the same as an exception or a log entry.
The reporting domain decides when a problem is an incident. Vuesion never creates incidents automatically from exceptions.
Every incident has a globally unique key. The key identifies the same recurring problem, so one problem results in one incident, no matter how often it is reported.
Build the key from the source, the type, and the affected resource, for example orders:invalid_status_transition:01J....
Domains report incidents through reportIncident of the incident service:
import { useIncidentService } from '#server/domain/incident/use-incident-service';
await useIncidentService().reportIncident({
key: `orders:invalid_status_transition:${order.id}`,
source: 'orders',
type: 'invalid_status_transition',
severity: 'ERROR',
title: 'Invalid order status transition',
description: 'A shipped order cannot move back to draft.',
context: { orderId: order.id, from: 'SHIPPED', to: 'DRAFT' },
});
firstSeenAt are kept. The occurrences are incremented, lastSeenAt is updated, and the severity, title, description, and context are replaced with the new report. An omitted description or context becomes null.Reporting is a single atomic upsert, so concurrent reports of the same key neither create duplicates nor lose occurrences.
The service does not catch database errors. The caller decides whether reporting is best effort, for example by catching and logging a failed report so that its own work can continue.
Like other services, the incident service accepts a transaction client, useIncidentService(tx). Incidents reported inside a transaction are rolled back with it.
resolveIncident(id) permanently deletes the incident, including its diagnostic context. No history is kept.
Resolving is idempotent: resolving an incident that no longer exists succeeds and changes nothing.
When the problem is reported again, a new incident is created with one occurrence.
The context is a JSON object with diagnostic information. Each domain defines its own context, and there is no fixed schema:
{ "integration": "erp", "endpoint": "inventory-sync", "httpStatus": 503 }
Incidents have no foreign keys to other models. Reference resources by their IDs in the context.
The incident service cannot know whether arbitrary JSON contains secrets. The caller is responsible for supplying safe data:
Billing reports incidents for problems that need an administrator, with the source billing and the severity ERROR. They supplement the existing billing warnings in the server logs, which are still written.
| Type | Key | Reported when |
|---|---|---|
unknown_subscription | billing:unknown-subscription:<subscriptionId> | The reconciliation detects an unknown provider subscription |
workspace_not_found | billing:sync:workspace_not_found:<subscriptionId> | The workspace of a subscription does not exist |
subscription_conflict | billing:sync:subscription_conflict:<subscriptionId> | A subscription would become current while another one is current |
unknown_price | billing:sync:unknown_price:<subscriptionId> | No plan price uses the provider price of a subscription |
<subscriptionId> is the External ID of the provider subscription. The synchronization types match the reasons of the synchronization warnings, and different problems of one subscription are separate incidents.
The context contains the available identifiers: provider (lemonsqueezy or demo), subscriptionId, externalPriceId, and providerStatus (the mapped Vuesion status). Synchronization incidents add the reason and the workspaceId, and a conflict adds the currentSubscriptionId. A webhook for a deleted workspace has no provider state, so its context only contains the provider, the subscription, the reason, and the workspace. An unknown subscription has no workspace, because only the signed billing reference of its webhook proves it; its context contains the customerEmail when the provider listed one. The context never contains provider responses, webhook payloads, billing references, or payment data.
Billing does not report incidents for invalid webhook signatures, invalid payloads, missing or invalid billing references, or transient provider failures such as timeouts, network errors, or HTTP 429, 502, and 503. Those are only logged or returned to the provider, which retries the webhook; the next reconciliation run retries known subscriptions.
Incidents are reported after the failed billing transaction, so they are not rolled back with it. Reporting is best effort: a failed report is logged with the incident key, and the billing operation, its response status, and the reconciliation of the other subscriptions stay unchanged.
Resolving a billing incident only deletes it. It does not repair the subscription, and a problem that still exists is reported again as a new incident: by the next hourly reconciliation run, or, for a failure only a webhook detects, by the next delivery of the webhook. See the subscription recovery runbook.
Platform admins manage incidents on the Incident Board at /admin/incidents. The Incidents entry in the Platform section of the sidebar is only visible to platform admins, and the /api/admin/incidents endpoints reject all other users.
The board lists all incidents by lastSeenAt, newest first, and can be filtered by:
Expand a row to investigate an incident. The details are loaded when the row is expanded and show all metadata, the full description, and the diagnostic context. Top-level context values are listed as key-value pairs, and nested objects and arrays are shown as formatted JSON. The context is read-only and is always rendered as text.
Incidents can be resolved from the expanded row, which deletes them.
The board translates its labels and the severity values. Incident content, such as the title, description, source, type, key, and context, is displayed exactly as reported. Vuesion's own integrations report incidents in English, and applications can report incidents in any language.
Continue with Development Workflow to learn how new features are typically built inside Vuesion and how the different tools and concepts come together during everyday development.