Build an app for Kismet
View .mdBuild a welcome offer, search introduction, or another configurable experience in your own repository. Use the SDK to store settings and engagement in Kismet, then show your controls and reports in the collection’s Apps directory. An outside developer can own the code and hosting while the manager keeps access, configuration, and performance in their Kismet account.
Your guest experience and manager panel are separate entry points. The guest
experience runs on the website; managers open the panel from
https://kismet.travel/account/collections/{collection}/software.
Give your app a recognizable identity
Section titled “Give your app a recognizable identity”Include presentation when you register a new app: its registration and branding
are saved together. Use the operation below to update branding on an existing app.
The Apps directory separates the app’s identity from its builder and connected services. Add a short description, an app icon, builder attribution, and integration logos. Use square logo assets with transparent or white backgrounds. Missing or unavailable logos render as initials; names remain visible.
import { createKismetClient as createAppClient } from '@kismet-tech/sdk/server';
const client = createAppClient({ baseUrl: 'https://api.ksmt.app/v1', apiKey: process.env.KISMET_SERVER_KEY!, collection: 'your-collection',});
await client.software.updatePresentation('welcome-offer', { description: 'Help guests discover and save a welcome offer.', iconUrl: 'https://assets.example.com/welcome-offer.png', builder: { name: 'Example Studio', logoUrl: 'https://assets.example.com/studio.png', websiteUrl: 'https://studio.example.com', }, integrations: [{ name: 'Offer provider', logoUrl: 'https://assets.example.com/provider.png', websiteUrl: 'https://provider.example.com', }],});Use client.software.presentation(moduleId) to read the current values. REST uses
GET and PUT /v1/developer/collections/{collection}/software/{module}/presentation.
The write replaces the complete presentation object: include every field you want
to keep. { integrations: [] } clears optional branding. It does not change configuration,
experiment revisions, permissions, or TEST/LIVE status.
Both operations require a server credential: telemetry.read for reads and
telemetry.configure for writes. Descriptions support up to 320 characters, names
160 characters, and up to 12 integrations. All image and website URLs must use HTTPS
without embedded credentials. Invalid values return 400; missing grants return
403, and a module outside the installation’s scope returns 404.
In an OAuth-connected coding client, use update_software_presentation with the
collection, installation ID, module ID, and presentation object. Preview without
confirm, review the current and proposed values, then apply with confirm: true.
This requires ADMIN or DEVELOPER access and an active authorized panel installation.
Attribution and integration names are developer-provided display information, not
verified endorsements or connection-health indicators.
Register from your coding agent
Section titled “Register from your coding agent”Connect the Developer MCP in your coding client and sign in with Kismet.
Ask it to set up a dedicated TEST installation for your collection with
telemetry.read and telemetry.configure; add telemetry.write only if the app
will record guest engagement. Approve the exact site and panel origins.
Use register_software_app with collection, installation_id, module_id,
and definition (the same object shown below). The tool previews the destination,
environment, and app definition without writing. Review that result, then call it
with confirm: true. It creates a disabled app and returns its dashboard URL.
No server key is needed in the prompt. Registration does not deploy the app,
connect to an integration vendor, or enable guest traffic.
For later branding changes use update_software_presentation. For app runtime
calls, store a restricted server credential in the hosting secret store; OAuth
management access and your deployed app’s machine access are separate.
Set up access
Section titled “Set up access”Use Kismet OAuth in your coding client to configure an installation. Register your site’s exact HTTPS origin, including the panel host. Use a restricted server credential in your server environment; never give it to the guest component or embedded panel.
| Capability | Operations |
|---|---|
telemetry.configure |
Register a module; update its configuration |
telemetry.read |
Read configuration and aggregate performance; redeem a manager panel launch on your server |
telemetry.write |
Enroll an eligible visit; record a display or action |
TEST and LIVE use separate installations and reports. Use TEST for QA. LIVE enrollment rejects non-production pages and classified bot traffic. Request scope comes from the credential; a URL or module ID cannot select another installation.
The collection administrator grants an outside developer access to that collection. The developer uses their own Kismet sign-in; managers do not share their login or password. Keep credentials in the site’s deployment secret store, not its source code or panel URL.
Connect your repository
Section titled “Connect your repository”- Create a TEST installation for the collection, with the three capabilities above and your exact staging and panel origins.
- Build the guest component and a separate, server-protected manager panel in your repository.
For example, use
/our/software/welcome-offer/panelfor the manager panel; the path itself does not grant or restrict access. - Register the module with its hosted panel URL and
enabled: false. Registration with an approved panel URL makes it discoverable in the collection’s Software dashboard; you do not need to modify Kismet’s frontend or upload a second report. - Open it from Kismet, verify manager access and configuration, then enable the TEST module when you are ready to exercise guest enrollment and events.
- Record actual displays and declared actions through the guest handlers below. Kismet stores and aggregates those receipts; the panel reads the same report.
The developer owns the website and panel presentation. Kismet owns the collection scope, current manager permissions, installation environment, revisioned configuration, visit assignment, and performance aggregation. This API reports software engagement; it is not an arbitrary metric-upload or general BI API.
Register a module
Section titled “Register a module”import { createKismetClient } from '@kismet-tech/sdk/server';
const kismet = createKismetClient({ apiKey: process.env.KISMET_SERVER_KEY!, baseUrl: 'https://api.ksmt.app/v1',});const configuration = await kismet.software.register('welcome-offer', { displayName: 'Welcome offer', panelUrl: 'https://staging.example.com/our/software/welcome-offer/panel', presentation: { description: 'Help guests discover and save a welcome offer.', builder: { name: 'Example Studio', websiteUrl: 'https://studio.example.com' }, integrations: [{ name: 'Offer provider', logoUrl: 'https://assets.example.com/provider.png' }], }, enabled: false, signedOutOnly: true, settingsSchema: { eligiblePage: { type: 'integer', label: 'Eligible page', minimum: 1, maximum: 2 }, }, variants: [ { id: 'first-page', label: 'First page', weight: 5000, settings: { eligiblePage: 1 } }, { id: 'second-page', label: 'Second page', weight: 5000, settings: { eligiblePage: 2 } }, ], actions: [{ id: 'claim', label: 'Claim selected' }],});REST equivalent: PUT /v1/developer/collections/{collection}/software/welcome-offer
with the same JSON and Authorization: Bearer <server-key>.
Registration declares immutable fields, actions, labels, and panel URL. An existing
module ID returns 409 MODULE_EXISTS; registration never overwrites an app.
After an interrupted response, read its configuration and presentation before
deciding whether to retry. Use the presentation operation for branding changes and
a new module for a changed definition. Managers can change declared setting values,
variant weights, enabled state, and the signed-out restriction—not executable
code, consent rules, prices, or guest entitlements. Weights total 10,000.
Registration does not deploy your panel or enable guest traffic. To appear in
Apps, its application and installation must be active, the installation
must grant collection-wide telemetry.read, and the panel must use an approved,
separate HTTPS origin. Use a panel URL without credentials, query parameters, or
fragments. A disabled module can still be reviewed in the dashboard.
Give managers one destination
Section titled “Give managers one destination”Acceptance before enabling
Section titled “Acceptance before enabling”- Open the returned Kismet dashboard URL and confirm the app name, description, builder, integration names, and TEST badge. Check narrow screens and broken-logo fallbacks.
- Open the panel from Kismet and verify both settings and performance. Direct visits must redirect to Kismet sign-in before rendering manager HTML.
- Test a read-only manager and a user without collection access. Neither can save; the unauthorized user must not see panel data.
- Enable the TEST configuration explicitly, record an actual display and declared action, and verify the report for that installation and revision. Unavailable conversion evidence is not zero conversions.
- Repeat registration separately for LIVE only after reviewing its origins, permissions, hosting, and guest flow. TEST registration does not promote to LIVE.
If the row is missing, check active application/installation state, collection-wide
telemetry.read, approved separate HTTPS panel origin, and the manager’s collection
role. Branding is optional and does not determine whether an app is authorized.
An integration logo is attribution—not proof that vendor credentials or connectivity work.
Use the SDK to link to Kismet rather than asking managers to find a private route on the website:
import { getSoftwareDashboardUrl } from '@kismet-tech/sdk/software';
const dashboardUrl = getSoftwareDashboardUrl({ collection: 'example-stays', moduleId: 'welcome-offer', installationId: '10000000-0000-4000-8000-000000000003',});// Use dashboardUrl for an "Open in Kismet" link on your site's manager entry page.// Omit moduleId and installationId to open the collection's Software index.Use the collection slug and installation ID from setup. The link contains no credential and grants no access: Kismet requires sign-in and checks the manager’s current collection role. An installation-specific link keeps TEST and LIVE panels unambiguous.
Connect the guest component
Section titled “Connect the guest component”Use the SDK’s browser client and Next handlers. The guest component sends only a page path and engagement receipts; the handler reads the existing Kismet session, forwards a server-held guest token when signed in, and enforces the site’s origin and CSRF protection. You do not need a second cookie or authentication flow.
Add software to your existing guest-account handler
configuration. Keep the same auth client, session secret, and allowed origins:
import { createKismetGuestAuthNextHandlers } from '@kismet-tech/sdk/next';
export const handlers = createKismetGuestAuthNextHandlers({ client: guestAuthClient, sessionSecret: process.env.KISMET_GUEST_SESSION_SECRET!, allowedOrigins: ['https://staging.example.com'], software: { client: kismet.software, modules: ['welcome-offer'], pageEnvironment: 'staging', },});guestAuthClient is the server client from your existing guest-account setup;
kismet is the server client above. modules explicitly enables the modules this
site serves. Set pageEnvironment in server configuration, not from browser
input. It describes the page; TEST or LIVE still comes from the installation.
Mount these three routes alongside the existing /api/kismet/auth/csrf route:
| Same-origin route | SDK handler |
|---|---|
GET /api/kismet/guest/software/[moduleId]/configuration |
handlers.softwareConfiguration(request, moduleId) |
POST /api/kismet/guest/software/[moduleId]/enrollments |
handlers.softwareEnroll(request, moduleId) |
POST /api/kismet/guest/software/[moduleId]/events |
handlers.softwareRecordEvent(request, moduleId) |
For example, in app/api/kismet/guest/software/[moduleId]/enrollments/route.ts:
import { handlers } from '@/lib/kismet-auth';
export async function POST( request: Request, context: { params: Promise<{ moduleId: string }> },) { const { moduleId } = await context.params; return handlers.softwareEnroll(request, moduleId);}Use the corresponding method and handler for configuration and events. Sites using Kismet’s managed guest relay need these routes enabled in that relay too; keep its existing verified-host request adapter. Registration, configuration writes, and performance reports are not guest relay operations.
In your guest component:
import { createKismetGuestBrowserClient } from '@kismet-tech/sdk/react';
const guest = createKismetGuestBrowserClient();const guestConfiguration = await guest.software.configuration('welcome-offer');// After auth resolves and the visit is eligible:const enrollment = await guest.software.enroll('welcome-offer', { pagePath: window.location.pathname,});// Only when the component has actually been displayed:await guest.software.recordEvent('welcome-offer', { pagePath: window.location.pathname, enrollmentId: enrollment.id, eventId: crypto.randomUUID(), kind: 'display',});The browser client obtains and submits CSRF proof automatically. The handler
uses the existing _kid_sid session, returns CANONICAL_SESSION_REQUIRED while
it is missing, and never creates an alternative visitor ID. Configure canonical
site identity before enrollment. Query strings and fragments are not forwarded
as page evidence. Malformed or expired guest cookies fail explicitly; they are
not interpreted as signed-out visits.
Keep staging visits separate from production
Section titled “Keep staging visits separate from production”Use a TEST installation and register your exact HTTPS staging hostname as a
verified staging domain for the same collection. The installation must also
allow that origin. Kismet’s authenticated site tracking uses the verified domain
registration to record staging page views as sandbox activity; a hostname prefix
or a browser-supplied pageEnvironment value is not sufficient.
TEST enrollment refuses a canonical session that already contains production
activity, even from another collection, with ENVIRONMENT_MISMATCH. Do not
replace _kid_sid, invent a visitor ID, or disable this check. Keep staging and
production browsing separate and use the supported site-identity integration.
Previously recorded activity is not relabeled when a domain is verified; an
existing mixed session remains ineligible for TEST enrollment.
Staging page views belong to the collection’s site tracking. They are not attributed to an arbitrary app installation. App enrollment and engagement receipts retain their own installation scope. No SDK upgrade or new enrollment parameter is required for this distinction.
Enroll before the display trigger
Section titled “Enroll before the display trigger”Wait until the canonical guest-auth helper resolves. Suppress new enrollment and
automatic display while auth is loading or unknown, or when signedOutOnly is
enabled and the guest is signed in. A claim flow already opened by the guest can
continue through its own sign-in; do not restart it as a new experiment.
Keep the current enrollment and an already-open claim mounted after its own successful login. The signed-out rule gates new enrollment, not completion of that claim. Continue engagement against the same enrollment; do not re-enroll or discard it merely because the auth helper now reports signed in.
Enroll on the first eligible page before checking the variant’s display condition. Count the first or second eligible page within that same visit. A visit that leaves before page two stays in the denominator. Enrollment is stable for the module, revision and session, and expires after 24 hours.
When the component is actually displayed, call software.recordEvent with the
same enrollment/session/page context, kind: 'display', and a UUID eventId.
For a declared action, use kind: 'action' and its actionId. Reuse the same event
ID when retrying a logical event; a second click gets a new event ID. The supported
handlers bind these requests to the canonical session and derive host, environment,
and user-agent evidence on the server.
Setting names are declared by the module, not predefined SDK fields. For example,
an existing module may use triggerPage instead of eligiblePage; consume that
declared name from enrollment.variant.settings without renaming or dropping it.
Connect the account panel
Section titled “Connect the account panel”The panel lives at the registered HTTPS URL. Keep it separate from guest-site navigation and guest telemetry. Its parent is the Kismet account host:
import { connectSoftwarePanel } from '@kismet-tech/sdk/software';
const panel = connectSoftwarePanel({ parentOrigin: 'https://kismet.travel' });const context = await panel.getContext();const current = await panel.getConfiguration();const performance = await panel.getPerformance({ from: '2026-09-01T00:00:00.000Z', to: '2026-09-02T00:00:00.000Z', revision: current.revision,});// On unmount: panel.dispose();The host selects the collection and installation and rechecks access for each
request. ADMIN and DEVELOPER can save when the installation grants configuration
access; MEMBER and VIEWER have read-only access. No account session, API key, or
raw guest record crosses into the iframe. Open the panel through Software in
the collection account; a standalone page reports HOST_UNAVAILABLE.
Protect the manager experience
Section titled “Protect the manager experience”Use Kismet account sign-in for managers, not the guest login on your website.
Render settings and reports only after the host has returned an authorized
context and the corresponding data. Before connection, show a loading state.
For HOST_UNAVAILABLE, show an Open in Kismet link using dashboardUrl; do not
render controls, cached reports, or sample performance. On access failure, clear
previously displayed data and offer a retry from Kismet. Recheck permissions when
loading or saving; an earlier successful connection is not a permanent grant.
Protect delivery of the page with authorizeSoftwarePanelRequest from the
server SDK. This early-access helper requires the panel-launch API and a matching
Kismet account host. It is separate from the browser bridge; installing a browser
component does not protect a server-rendered page.
Kismet signs the manager in and checks their current collection role before opening the registered panel with a single-use, 30-second render code in a form POST body, never a URL. Your server redeems that code with its existing installation credential. Kismet checks the manager’s current role again, along with the active application, installation, grants and exact registered URL. No manager password, account session, user ID or role claim is passed to your site. No separate website password is required.
import { authorizeSoftwarePanelRequest } from '@kismet-tech/sdk/server';
// Call before rendering the manager page, including nested or preview routes.export async function authorizeManagerPage(request: Request) { return authorizeSoftwarePanelRequest({ software: kismet.software, request, panelUrl: 'https://staging.example.com/our/software/welcome-offer/panel', collection: 'example-stays', moduleId: 'welcome-offer', installationId: '10000000-0000-4000-8000-000000000003', environment: 'TEST', });}When authorized is false, use a 303 redirect to the returned redirectTo without
rendering the panel. The destination is your module’s Kismet Software dashboard,
where a signed-out manager can sign in. Render the panel only when authorized
is true, then load settings and reports through the browser bridge. Surface
service failures and server-credential configuration errors without rendering
the panel; do not turn them into a repeated sign-in redirect.
The registered URL, collection, installation and environment come from your
server configuration, never from the query or an untrusted forwarded host. Pass
the complete Request (or a clone if your framework needs to read it again).
The helper accepts only a POST from https://kismet.travel to the exact,
query-free panel URL, with application/x-www-form-urlencoded content and a
single kismet_panel_launch field. Duplicate fields, additional fields and bodies
larger than 256 bytes are refused before redemption. Your reverse proxy must
preserve this bounded POST body, content type and Origin; it must not turn the
body into a query string. A 303 denial prevents forwarding the POST body to the
sign-in destination. Direct GET/HEAD visits may use a fixed 307 redirect.
For Next.js, guard the route in Proxy/Middleware before rendering a
force-dynamic page. Reject Server Action, RSC and prefetch requests at this
entry point. After successful redemption, continue the ordinary form POST to the
page; do not redirect to an unprotected GET or create a website session. Keep
the browser bridge attached before submitting the form into its named iframe.
The legacy requestUrl query-code adapter remains for installed clients;
migrate to request to avoid exposing render codes to URL logging.
REST adapters can call POST /v1/developer/collections/{collection}/software/{module}/panel-launches/redeem
with { code, panelUrl } and a server credential granting telemetry.read.
The code authorizes one page render, not a website session or data access. It cannot be reused after reload; reopen the panel from Kismet for a new code. The flow needs no third-party cookies. Do not prefetch, cache, log, or send the launch body to analytics. Do not record request bodies on the launch route. The clean panel URL contains no credential and requires no history scrubbing. A previously issued code expires within 30 seconds; signing out does not create or retain a session on your website. Collection-role revocation is checked at redemption and on every subsequent data operation.
Do not include manager data in HTML, page props, static files, or unauthenticated
API responses. Do not add a server-key reporting endpoint to make a standalone
page work. Static JavaScript assets are not confidential; protect data and the
page-render entry, including alternate paths. Never use a path, Referer, or
iframe detection as authentication.
Keep manager pages out of guest navigation and guest telemetry. Set
Cache-Control: private, no-store, Referrer-Policy: no-referrer, and
X-Robots-Tag: noindex, nofollow on manager responses, including redirects and
errors. Set Content-Security-Policy: frame-ancestors https://kismet.travel
(or the explicitly configured Kismet account origin). Disable framework static
generation and shared caching for these routes. These settings complement the
server authorization check.
To save, call panel.updateConfiguration with expectedRevision, enabled,
signedOutOnly, and the existing variant IDs, weights and settings. Read back the
result. On REVISION_CONFLICT, reload and ask the manager to review their changes
instead of overwriting another editor.
Display performance accurately
Section titled “Display performance accurately”Use software.performance on your server, or panel.getPerformance in the account
panel. Supply one revision and an elapsed UTC interval of at most 31 days.
The API returns all retained receipts for that cohort, not a recent-event sample.
assignedVisitsis the denominator; it counts canonical sessions, not people.displayedVisitsand actionvisitscount distinct assigned visits. Actioneventscounts deduplicated occurrences.- Show period, revision,
generatedAt,lastEventAt, and coverage. Complete coverage means all retained received events, not proof of every browser event. - Marketing signup and confirmed-booking attribution are currently unavailable:
their counts and rates are
null. Do not report a claim or submit as conversion.
Expired sessions, missing modules, stale revisions, revoked credentials or grants, and invalid origin/environment evidence are refused explicitly. A failed or unavailable report is not zero performance. None of these operations sends email, changes consent, creates a booking, or authorizes payment.
See the method-labelled endpoints under API reference → Software for request schemas, response contracts, limits, and problem codes.
Verify the handoff
Section titled “Verify the handoff”- An authorized manager opens the registered TEST panel from the collection’s Software dashboard. An unrelated collection cannot read or change it.
- Opening the website’s panel URL directly redirects to Kismet before the panel HTML is served. Missing, expired, replayed and wrong-installation render codes are refused. A signed-in guest cannot substitute for a Kismet manager.
- The panel opens with third-party cookies blocked. A service outage does not expose the page or cause a sign-in loop; leaving the account view removes the bridge and frame. Keep server credentials and launch URLs out of logs.
- Read-only managers can see permitted reports but cannot save. Revoking access prevents subsequent reads and writes, including in a panel that is already open.
- A saved configuration is read back with its new revision. Concurrent changes
produce
REVISION_CONFLICT, not a silent overwrite. - After enabling TEST and completing a test visit, the dashboard reads the same
module, installation, revision, and period as
software.performanceon the server. No report import or client-calculated total is required. - A failed request is an error, not a zero report. Missing verified conversion counts stay unavailable; TEST receipts never become LIVE performance.