@truesift/svelte
This guide covers @truesift/svelte (Svelte 5 / SvelteKit) only, version 0.2.0. For React / Next.js use the separate Next.js SDK documentation.
Official TrueSift SDK for Svelte 5 and SvelteKit.
@truesift/svelte provides human verification for forms, registrations, checkout, booking, and other protected actions. It combines a responsive Svelte component, same-origin verification endpoints, and server-side validation of short-lived, single-use verification proofs.
The SDK follows the TrueSift verification protocol used by @truesift/next, with native Svelte 5 APIs and an additional half banner size.
Official TrueSift Links
- Website: truesift.de
- Create an account: TrueSift registration
- Sign in: TrueSift login
- Request access and obtain your Site Key / Secret Key: TrueSift access
- Documentation: TrueSift documentation
- Svelte SDK on npm: @truesift/svelte
Features
- Native Svelte 5 verification component and reactive controller.
- SvelteKit server handlers for challenge creation and browser verification.
- Server-only business proof validation through
verifyTrueSiftProof. - Four visual modes:
checkbox,banner,inline, andbadge. - Four sizes:
normal,compact,flexible, andhalf(500 px maximum-width banner). - Three themes:
auto,light, anddark. - English, German, and Bulgarian interface labels, automatic language detection, and custom label overrides.
- Bundled TrueSift shield animation, local dotLottie WebAssembly runtime, reduced-motion handling, and SVG fallback.
- Privacy-conscious, aggregate Behavioral Signals v2 (compatible with legacy v1 payloads).
- Same-origin browser-to-application requests. TrueSift credentials and upstream requests stay on the server.
- Explicit handling of
allow,review,block, rejected, and unavailable results. - Fail-closed verification by default; configurable server-side fail-open policy.
Installation
pnpm add @truesift/svelteOr:
npm install @truesift/svelteRequirements: Svelte 5. For the examples below, use a SvelteKit application with server routes. The package declares Node.js >=20.19.0 in its engine metadata.
Public Entry Points
Component and reactive controller:
import { TrueSiftCheck, createTrueSiftCheck } from '@truesift/svelte/client';The component and controller are also exported from the package root:
import { TrueSiftCheck } from '@truesift/svelte';Server endpoints and proof validation:
import {
createTrueSiftChallengeHandler,
createTrueSiftVerifyHandler,
verifyTrueSiftProof
} from '@truesift/svelte/server';Shared protocol types:
import type { TrueSiftClientVerificationResult } from '@truesift/svelte';
import type { VerifyTrueSiftProofResult } from '@truesift/svelte/server';Available package paths:
| Entry point | Purpose |
|---|---|
@truesift/svelte | Component, controller, browser-facing types and constants |
@truesift/svelte/client | Component, controller, localization, and visual helpers |
@truesift/svelte/server | Server-only handlers, configuration, and proof verification |
@truesift/svelte/shared | Shared protocol types and constants |
@truesift/svelte/styles.css | Optional explicit stylesheet; the component already imports its styles |
Do not import /server into browser components. Never expose your TrueSift Secret Key to the client.
Environment Variables
Configure the application server with credentials issued for your TrueSift site:
TRUESIFT_API_BASE_URL=https://api.truesift.de
TRUESIFT_SITE_KEY=your_site_key
TRUESIFT_SECRET_KEY=your_secret_key
TRUESIFT_DEFAULT_ACTION=your_registered_action
TRUESIFT_FAIL_OPEN=false
TRUESIFT_TIMEOUT_MS=2500| Variable | Purpose |
|---|---|
TRUESIFT_API_BASE_URL | TrueSift API origin; do not append /api/v1 or a specific endpoint |
TRUESIFT_SITE_KEY | Site key for your application |
TRUESIFT_SECRET_KEY | Private secret key; server only |
TRUESIFT_DEFAULT_ACTION | Default action used by the server; align it with the action used by the client |
TRUESIFT_FAIL_OPEN | Server-side availability policy; false is recommended for protected transactions |
TRUESIFT_TIMEOUT_MS | Server request timeout in milliseconds; accepted range: 250–30000 |
TRUESIFT_SITE_KEY and TRUESIFT_SECRET_KEY are required. The SDK can resolve configuration from server environment variables or from an explicit TrueSiftServerConfigInput. The default timeout is 2500 ms, and the SDK's default failOpen setting is false.
With SvelteKit, access secrets via $env/dynamic/private or an equivalent server-only configuration source. *Never place credentials in `PUBLIC_` variables or ship them to the browser.**
Verification Flow
A protected action has two separate stages:
Browser / Svelte component
|
+--> POST /api/truesift/challenge (same-origin SvelteKit route)
| +--> TrueSift challenge service (server-to-server)
|
+--> POST /api/truesift/verify (same-origin SvelteKit route)
+--> TrueSift browser verification service (server-to-server)
+--> verificationToken (when issued)
|
+--> POST /api/contact (your protected business route)
+--> verifyTrueSiftProof(verificationToken, trusted context)
+--> TrueSift proof service (server-to-server)
+--> execute the business operation only after valid proof- Browser verification:
TrueSiftCheckrequests a challenge, collects permitted verification signals, and receives a verification decision from the application's own routes. A response may include a short-livedverificationToken. - Business proof validation: the protected server route consumes the token with
verifyTrueSiftProof. A client-sideallowresult is not sufficient authorization. Only a successful server-side proof check permits the protected operation to continue.
A verification token is opaque, short-lived, and single-use. After any consumed attempt, start a new verification rather than reusing the token.
SvelteKit Setup
The browser calls only local SvelteKit routes. The server helpers call TrueSift upstream; no direct browser connection to the TrueSift API is required.
1. Challenge route
Create src/routes/api/truesift/challenge/+server.ts:
import { env } from '$env/dynamic/private';
import type { RequestHandler } from './$types';
import { createTrueSiftChallengeHandler } from '@truesift/svelte/server';
const handleChallenge = createTrueSiftChallengeHandler({
apiBaseUrl: env.TRUESIFT_API_BASE_URL,
siteKey: env.TRUESIFT_SITE_KEY,
secretKey: env.TRUESIFT_SECRET_KEY,
defaultAction: env.TRUESIFT_DEFAULT_ACTION,
failOpen: false
});
export const POST: RequestHandler = ({ request }) => handleChallenge(request);2. Verify route
Create src/routes/api/truesift/verify/+server.ts:
import { env } from '$env/dynamic/private';
import type { RequestHandler } from './$types';
import { createTrueSiftVerifyHandler } from '@truesift/svelte/server';
const handleVerify = createTrueSiftVerifyHandler({
apiBaseUrl: env.TRUESIFT_API_BASE_URL,
siteKey: env.TRUESIFT_SITE_KEY,
secretKey: env.TRUESIFT_SECRET_KEY,
defaultAction: env.TRUESIFT_DEFAULT_ACTION,
failOpen: false
});
export const POST: RequestHandler = ({ request }) => handleVerify(request);Both routes expect JSON POST requests and enforce same-origin request checks. The default browser paths are /api/truesift/challenge and /api/truesift/verify.
3. Provide a trusted action to the page
For a protected page at /contact, create src/routes/contact/+page.server.ts:
import { env } from '$env/dynamic/private';
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = () => {
const action = env.TRUESIFT_DEFAULT_ACTION?.trim();
if (!action) error(500, 'TrueSift action is not configured');
return { truesiftAction: action };
};Use an action identifier configured for your site. The page and protected route must agree on the same action, page path, and origin.
4. Add the verification component
Create or update src/routes/contact/+page.svelte:
<script lang="ts">
import { TrueSiftCheck } from '@truesift/svelte/client';
import type { TrueSiftClientVerificationResult } from '@truesift/svelte';
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
let verificationToken = $state<string | null>(null);
let submitting = $state(false);
let message = $state('');
function handleVerified(result: TrueSiftClientVerificationResult): void {
verificationToken =
result.status === 'verified' &&
result.allowed &&
result.decision === 'allow'
? result.verificationToken ?? null
: null;
}
async function submitContact(): Promise<void> {
if (!verificationToken || submitting) return;
const token = verificationToken;
verificationToken = null; // A verification token cannot be reused.
submitting = true;
message = '';
try {
const response = await fetch('/api/contact', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
verificationToken: token
// Include validated contact fields from your form.
})
});
message = response.ok
? 'Verification accepted.'
: 'Verification could not be completed. Please try again.';
} catch {
message = 'The request could not be completed. Please verify again.';
} finally {
submitting = false;
}
}
</script>
<TrueSiftCheck
action={data.truesiftAction}
path="/contact"
visualMode="checkbox"
size="normal"
theme="auto"
locale="auto"
onVerified={handleVerified}
onError={() => (verificationToken = null)}
/>
<button type="button" disabled={!verificationToken || submitting} onclick={submitContact}>
Submit
</button>
<p role="status">{message}</p>The button is gated for user experience, but the protected server route is the security boundary. Add your form inputs and normal business validation to the protected action.
5. Validate the one-time proof on the server
In src/routes/api/contact/+server.ts, consume the proof before performing the protected action:
import { json } from '@sveltejs/kit';
import { env } from '$env/dynamic/private';
import type { RequestHandler } from './$types';
import { verifyTrueSiftProof } from '@truesift/svelte/server';
export const POST: RequestHandler = async ({ request, url }) => {
if (request.headers.get('origin') !== url.origin) {
return json({ error: 'Invalid request origin' }, { status: 403 });
}
if (!request.headers.get('content-type')?.includes('application/json')) {
return json({ error: 'Unsupported content type' }, { status: 415 });
}
const body: unknown = await request.json().catch(() => null);
if (typeof body !== 'object' || body === null || Array.isArray(body)) {
return json({ error: 'Invalid request' }, { status: 400 });
}
const { verificationToken } = body as Record<string, unknown>;
const expectedAction = env.TRUESIFT_DEFAULT_ACTION?.trim();
if (!expectedAction) {
return json({ error: 'Verification is unavailable' }, { status: 503 });
}
const proof = await verifyTrueSiftProof(
{
verificationToken: typeof verificationToken === 'string' ? verificationToken : '',
expectedAction,
expectedPath: '/contact',
expectedOrigin: url.origin
},
{
apiBaseUrl: env.TRUESIFT_API_BASE_URL,
siteKey: env.TRUESIFT_SITE_KEY,
secretKey: env.TRUESIFT_SECRET_KEY,
failOpen: false
}
);
if (!proof.ok || !proof.allowed || proof.failOpen) {
return json(
{ error: 'Verification rejected' },
{ status: proof.reason === 'unavailable' ? 503 : 403 }
);
}
// The verification gate has passed. Validate and process the
// contact request here, using your application's business logic.
return json({ verified: true });
};Important: This endpoint demonstrates the TrueSift verification gate. It does not send an email, save a contact request, or implement another business operation. The application must add its actual input validation, business logic, and persistence after proof validation. The verified: true response in this example reports the proof check, not a completed contact submission. Do not treat it as a complete contact endpoint until the business operation has been implemented.
Do not copy expectedAction, expectedPath, or expectedOrigin from browser-supplied JSON. They belong to the trusted server route.
Visual Modes
Every mode uses the same challenge, verification, and proof protocol. Only presentation changes.
visualMode | Recommended use |
|---|---|
checkbox | Contact forms, registration, checkout, and standard verification blocks |
banner | Prominent verification sections, large forms, or confirmation panels |
inline | Compact placement inside existing content or form layouts |
badge | Small verification status indicators |
<TrueSiftCheck action={data.truesiftAction} visualMode="checkbox" />
<TrueSiftCheck action={data.truesiftAction} visualMode="banner" />
<TrueSiftCheck action={data.truesiftAction} visualMode="inline" />
<TrueSiftCheck action={data.truesiftAction} visualMode="badge" />Sizes
The Svelte SDK supports four sizes:
size | Description |
|---|---|
normal | Default, balanced widget size |
compact | Reduced spacing and smaller visual presentation |
flexible | Uses the available container width |
half | Svelte-specific: responsive banner with maximum width 500 px |
<TrueSiftCheck action={data.truesiftAction} visualMode="checkbox" size="normal" />
<TrueSiftCheck action={data.truesiftAction} visualMode="checkbox" size="compact" />
<TrueSiftCheck action={data.truesiftAction} visualMode="banner" size="flexible" />
<TrueSiftCheck action={data.truesiftAction} visualMode="banner" size="half" />Half-width banner (Svelte SDK)
size="half" is an additional visual size not available in @truesift/next v0.4.0. Its dedicated styling applies to visualMode="banner" only. It limits the banner to 500 px while remaining responsive on narrower screens. Other visual modes do not receive a special half-width layout.
<TrueSiftCheck
action={data.truesiftAction}
path="/contact"
visualMode="banner"
size="half"
theme="auto"
locale="de"
/>Themes
theme | Description |
|---|---|
auto | Follows the browser's preferred color scheme; default |
light | Light appearance |
dark | Dark appearance |
<TrueSiftCheck action={data.truesiftAction} theme="auto" />
<TrueSiftCheck action={data.truesiftAction} theme="light" />
<TrueSiftCheck action={data.truesiftAction} theme="dark" />Themes affect the widget appearance, not the verification decision.
Localization
locale | Language |
|---|---|
auto | Browser language with English fallback; default |
en | English |
de | German |
bg | Bulgarian |
<TrueSiftCheck action={data.truesiftAction} locale="auto" />
<TrueSiftCheck action={data.truesiftAction} locale="en" />
<TrueSiftCheck action={data.truesiftAction} locale="de" />
<TrueSiftCheck action={data.truesiftAction} locale="bg" />Pass an explicit locale when your application has a language switcher; auto uses browser language detection.
Custom labels
<TrueSiftCheck
action={data.truesiftAction}
locale="de"
labels={{
idle: 'Sicherheitsprüfung ist bereit.',
checking: 'Sicherheitsprüfung läuft …',
verified: 'Sicherheitsprüfung abgeschlossen.',
failed: 'Bitte versuchen Sie es erneut.',
unavailable: 'Sicherheitsprüfung derzeit nicht verfügbar.',
meta: 'Datenschutzfreundliche Verifizierung',
retry: 'Erneut versuchen',
checkboxLabel: 'Menschliche Interaktion bestätigen'
}}
/>All label properties are optional. The component fills missing values from the selected built-in locale. The TrueSift attribution line is part of the widget's visual identity and is not translated.
Component Props
TrueSiftCheck implements TrueSiftCheckProps:
| Prop | Type | Default | Description |
|---|---|---|---|
action | string | Required | Action identifier; must match the protected server route |
path | string | Current pathname + query | Page path to bind to the verification proof |
origin | string | Current origin | Optional explicit origin override |
challengeUrl | string | /api/truesift/challenge | Same-origin application challenge endpoint |
verifyUrl | string | /api/truesift/verify | Same-origin application verify endpoint |
autoStart | boolean | true | Start the verification automatically on mount |
disabled | boolean | false | Disable verification requests |
className | string | None | Additional CSS class on the widget root |
visualMode | checkbox, banner, inline, badge | checkbox | Widget layout |
size | normal, compact, flexible, half | normal | Widget size; half affects only banners |
theme | auto, light, dark | auto | Widget color theme |
locale | auto, en, de, bg | auto | Interface language |
labels | TrueSiftCheckLabels | Localized labels | Override individual visible labels |
lottie | TrueSiftCheckLottieConfig | Bundled animation | Animation source, playback, and fallback configuration |
onReady | () => void | None | Fired after component mount |
onVerified | (result: TrueSiftClientVerificationResult) => void | None | Fired for completed verification responses, including review, block, and fail-open |
onError | (error: TrueSiftClientError) => void | None | Fired for request, configuration, or network failures |
onVerified does not mean access has been granted. Check status, allowed, decision, and the presence of a verificationToken; then consume that token server-side.
Manual verification and reset
Set autoStart={false} and use the public component methods through Svelte 5 bind:this:
<script lang="ts">
import { TrueSiftCheck } from '@truesift/svelte/client';
import type { TrueSiftCheckActions } from '@truesift/svelte/client';
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
let check = $state<TrueSiftCheckActions | undefined>();
</script>
<TrueSiftCheck bind:this={check} action={data.truesiftAction} autoStart={false} />
<button type="button" onclick={() => void check?.start()}>Verify</button>
<button type="button" onclick={() => check?.reset()}>Reset</button>start() resolves to a TrueSiftClientVerificationResult or undefined; reset() clears the current attempt. A reset does not make an already consumed proof reusable.
Headless Svelte 5 Controller
Use createTrueSiftCheck to handle verification without rendering the official widget. It uses the same protocol and reactive state as the component.
<script lang="ts">
import { onMount } from 'svelte';
import { createTrueSiftCheck } from '@truesift/svelte/client';
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
const check = createTrueSiftCheck(() => ({
action: data.truesiftAction,
path: '/contact'
}));
onMount(() => {
check.activate();
return () => check.destroy();
});
</script>
<button type="button" disabled={check.isLoading} onclick={() => void check.start()}>
Verify
</button>
<p aria-live="polite">{check.state}</p>The controller exposes state, challenge, verification, error, isLoading, and the methods activate(), start(), reset(), and destroy(). Initialize it in the component and clean it up on unmount.
Animation and Reduced Motion
The TrueSift shield animation ships with the npm package. The component uses the bundled animation and a locally resolved dotLottie WebAssembly asset by default; no external animation CDN is required.
The component responds to verification states, respects the user's reduced-motion preference, and displays an SVG fallback when animation cannot be displayed.
<TrueSiftCheck
action={data.truesiftAction}
lottie={{ enabled: true, fallback: 'svg' }}
/>TrueSiftCheckLottieConfig additionally supports custom src, type (dotlottie or json), autoplay, loop, and optional stateMachine settings. Custom animation is optional; no asset path is required for the official shield.
Content Security Policy: The local WebAssembly runtime may require script-src 'self' 'wasm-unsafe-eval' alongside your application's existing policy. Do not add generic 'unsafe-eval' or external animation CDN permissions for this SDK. Configure CSP according to your hosting environment and other application requirements.
Behavioral Signals v2
@truesift/svelte@0.2.0 uses the aggregate-only Behavioral Signals v2 protocol aligned with @truesift/next@0.4.0. Legacy v1 telemetry remains accepted. After the challenge, the controller uses a cancellable 2,500 ms observation window (measured from the beginning of the verification attempt), with a per-challenge baseline. Reset, component unmount and cancellation stop pending verification and remove listeners.
The transient snapshot may contain telemetryVersion: 2, elapsedMs, interactionCount, challengeInteractionCount, pointerMoveCount, pointerDownCount, scrollCount, touchCount, keyPressCount, focusCount, blurCount, visibilityChangeCount, firstInteractionDelayMs, and activeDurationMs. Pointer movement and scrolling are sampled every 75 ms; counters are capped at 1,000. A zero-interaction snapshot requires at least 1,000 ms of visible observation. No individual interaction establishes whether a visitor is human.
No key values, pointer coordinates, field values, DOM targets, fingerprints or persistent visitor identifiers are collected. Only aggregate telemetry passes through the same-origin SvelteKit BFF. For referrer and currentPageUrl, the SDK sends origins only, without paths, queries or fragments. By default, the proof path is the pathname without the query string; an explicitly configured path is preserved for existing expectedPath contracts. Avoid credentials in custom paths.
SvelteKit BFF security in 0.2.0
- Local challenge and verify handlers accept JSON
POSTonly, reject bodies exceeding 32 KiB, validate browserOriginstrictly, and enforce same-origin Fetch Metadata when supplied. Reverse proxies must preserve the public request origin. - Behavioral values are bounded and validated before forwarding; client-controlled context is excluded from upstream warning logs. Upstream errors are sanitized without exposing raw messages.
- Remote TrueSift upstreams require HTTPS; HTTP is allowed only on loopback during local development (
localhost,127.0.0.1,[::1]). - BFF responses include
Cache-Control: no-storeandX-Content-Type-Options: nosniff. - Inconsistent verification decisions and malformed or contradictory proof responses are rejected; protocol errors must not become fail-open authorization.
- The protected business route must still validate and consume the single-use proof server-side. A browser decision or telemetry snapshot never authorizes the business action.
Release notes: 0.2.0 (2026-10-12)
Aligned Behavioral Signals v2 with Next SDK 0.4.0; added cancellable observation, stricter origin and request-size controls, URL privacy safeguards, safer upstream error handling, and adversarial regression coverage. The Svelte 5 component API and existing same-origin POST/JSON integration remain unchanged.
Server Configuration
Server handlers and verifyTrueSiftProof accept:
type TrueSiftServerConfigInput = {
readonly apiBaseUrl?: string;
readonly siteKey?: string;
readonly secretKey?: string;
readonly defaultAction?: string;
readonly failOpen?: boolean;
readonly timeoutMs?: number;
};Values supplied explicitly take precedence over server environment values. Missing optional values use the SDK defaults. The API base URL should contain only the service origin, not /api/v1 or route paths.
failOpen is server-controlled. Use failOpen: false when protected transactions require a verified proof before continuing.
Proof Input Contract
Import verifyTrueSiftProof exclusively from @truesift/svelte/server:
import { verifyTrueSiftProof } from '@truesift/svelte/server';It accepts:
type VerifyTrueSiftProofInput = {
readonly verificationToken: string;
readonly expectedAction?: string;
readonly expectedPath?: string;
readonly expectedOrigin?: string;
};Only the token comes from the client. The route must define its expected action, page path, and origin independently. A missing or empty token produces a missing result without contacting the TrueSift API.
Proof Result Contract
The server helper returns VerifyTrueSiftProofResult, a discriminated union:
Authoritatively allowed
{
ok: true;
allowed: true;
decision: 'allow';
failOpen: false;
challengeId: string;
action: string;
origin: string;
path: string | null;
consumedAt: string;
}Rejected proof
{
ok: false;
allowed: false;
reason: 'missing' | 'invalid' | 'expired' | 'replayed' | 'mismatch';
failOpen: false;
}Review or block decision
{
ok: false;
allowed: false;
reason: 'blocked';
decision: 'review' | 'block';
failOpen: false;
}Service unavailable, fail-closed
{ ok: false; allowed: false; reason: 'unavailable'; failOpen: false }Service unavailable, fail-open
{ ok: false; allowed: true; reason: 'unavailable'; failOpen: true }For an action requiring authoritative proof, proceed only when proof.ok && proof.allowed && !proof.failOpen. A fail-open result is not a verified proof, even if its allowed property is true.
Authentication, Service Errors, and Fail-Open
- Invalid credentials (
401) and inactive or forbidden sites (403) are not ordinary proof rejection results. Non-retryable configuration or upstream errors throw aTrueSiftError. - Network failures, timeouts, and retryable service errors produce
reason: 'unavailable'when the server helper can apply its configured availability policy. failOpen: trueonly applies to eligible service-unavailable conditions. It never turnsmissing,invalid,expired,replayed,mismatch, orblockedinto an authoritative proof.- The default is fail-closed (
failOpen: false). Set a different policy only after assessing the security requirements of the protected business action.
Security Notes
- Keep
TRUESIFT_SECRET_KEYon the server. Never embed it in Svelte components orPUBLIC_*variables. - Browser requests use same-origin application endpoints, not direct requests to the upstream TrueSift service.
- Verify the returned
verificationTokenin the final business route. UI state must never authorize the operation by itself. - Define
expectedAction,expectedPath, andexpectedOriginfrom trusted server-side application context. - Never trust browser-provided
allowed,decision,score,status,failOpen,challengeId,action,path, ororiginas proof. - Treat verification tokens as short-lived and single-use; require a fresh check for any retry after proof consumption.
- Preserve normal application security controls: authorization, CSRF protection, request validation, rate limiting, idempotency, and business transaction checks.
License
SDK source code and package implementation: MIT License. See the LICENSE file included with the npm package.
Third-party animation asset:
- Author: Maxence Chanel
- License: Free to use under the Lottie Simple License
- Usage: Bundled visual asset for TrueSift verification states
The third-party animation asset is not authored by WebDigiTech and is not licensed under the MIT License by WebDigiTech. Its use remains subject to the terms of the Lottie Simple License and the original asset licensing conditions.