Documentation

@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.

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, and badge.
  • Four sizes: normal, compact, flexible, and half (500 px maximum-width banner).
  • Three themes: auto, light, and dark.
  • 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

bash
pnpm add @truesift/svelte

Or:

bash
npm install @truesift/svelte

Requirements: 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:

ts
import { TrueSiftCheck, createTrueSiftCheck } from '@truesift/svelte/client';

The component and controller are also exported from the package root:

ts
import { TrueSiftCheck } from '@truesift/svelte';

Server endpoints and proof validation:

ts
import {
  createTrueSiftChallengeHandler,
  createTrueSiftVerifyHandler,
  verifyTrueSiftProof
} from '@truesift/svelte/server';

Shared protocol types:

ts
import type { TrueSiftClientVerificationResult } from '@truesift/svelte';
import type { VerifyTrueSiftProofResult } from '@truesift/svelte/server';

Available package paths:

Entry pointPurpose
@truesift/svelteComponent, controller, browser-facing types and constants
@truesift/svelte/clientComponent, controller, localization, and visual helpers
@truesift/svelte/serverServer-only handlers, configuration, and proof verification
@truesift/svelte/sharedShared protocol types and constants
@truesift/svelte/styles.cssOptional 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:

dotenv
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
VariablePurpose
TRUESIFT_API_BASE_URLTrueSift API origin; do not append /api/v1 or a specific endpoint
TRUESIFT_SITE_KEYSite key for your application
TRUESIFT_SECRET_KEYPrivate secret key; server only
TRUESIFT_DEFAULT_ACTIONDefault action used by the server; align it with the action used by the client
TRUESIFT_FAIL_OPENServer-side availability policy; false is recommended for protected transactions
TRUESIFT_TIMEOUT_MSServer 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:

text
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
  1. Browser verification: TrueSiftCheck requests a challenge, collects permitted verification signals, and receives a verification decision from the application's own routes. A response may include a short-lived verificationToken.
  2. Business proof validation: the protected server route consumes the token with verifyTrueSiftProof. A client-side allow result 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:

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:

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:

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:

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:

ts
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.

visualModeRecommended use
checkboxContact forms, registration, checkout, and standard verification blocks
bannerProminent verification sections, large forms, or confirmation panels
inlineCompact placement inside existing content or form layouts
badgeSmall verification status indicators
svelte
<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:

sizeDescription
normalDefault, balanced widget size
compactReduced spacing and smaller visual presentation
flexibleUses the available container width
halfSvelte-specific: responsive banner with maximum width 500 px
svelte
<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.

svelte
<TrueSiftCheck
  action={data.truesiftAction}
  path="/contact"
  visualMode="banner"
  size="half"
  theme="auto"
  locale="de"
/>

Themes

themeDescription
autoFollows the browser's preferred color scheme; default
lightLight appearance
darkDark appearance
svelte
<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

localeLanguage
autoBrowser language with English fallback; default
enEnglish
deGerman
bgBulgarian
svelte
<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

svelte
<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:

PropTypeDefaultDescription
actionstringRequiredAction identifier; must match the protected server route
pathstringCurrent pathname + queryPage path to bind to the verification proof
originstringCurrent originOptional explicit origin override
challengeUrlstring/api/truesift/challengeSame-origin application challenge endpoint
verifyUrlstring/api/truesift/verifySame-origin application verify endpoint
autoStartbooleantrueStart the verification automatically on mount
disabledbooleanfalseDisable verification requests
classNamestringNoneAdditional CSS class on the widget root
visualModecheckbox, banner, inline, badgecheckboxWidget layout
sizenormal, compact, flexible, halfnormalWidget size; half affects only banners
themeauto, light, darkautoWidget color theme
localeauto, en, de, bgautoInterface language
labelsTrueSiftCheckLabelsLocalized labelsOverride individual visible labels
lottieTrueSiftCheckLottieConfigBundled animationAnimation source, playback, and fallback configuration
onReady() => voidNoneFired after component mount
onVerified(result: TrueSiftClientVerificationResult) => voidNoneFired for completed verification responses, including review, block, and fail-open
onError(error: TrueSiftClientError) => voidNoneFired 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:

svelte
<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.

svelte
<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.

svelte
<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 POST only, reject bodies exceeding 32 KiB, validate browser Origin strictly, 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-store and X-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:

ts
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:

ts
import { verifyTrueSiftProof } from '@truesift/svelte/server';

It accepts:

ts
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

ts
{
  ok: true;
  allowed: true;
  decision: 'allow';
  failOpen: false;
  challengeId: string;
  action: string;
  origin: string;
  path: string | null;
  consumedAt: string;
}

Rejected proof

ts
{
  ok: false;
  allowed: false;
  reason: 'missing' | 'invalid' | 'expired' | 'replayed' | 'mismatch';
  failOpen: false;
}

Review or block decision

ts
{
  ok: false;
  allowed: false;
  reason: 'blocked';
  decision: 'review' | 'block';
  failOpen: false;
}

Service unavailable, fail-closed

ts
{ ok: false; allowed: false; reason: 'unavailable'; failOpen: false }

Service unavailable, fail-open

ts
{ 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 a TrueSiftError.
  • Network failures, timeouts, and retryable service errors produce reason: 'unavailable' when the server helper can apply its configured availability policy.
  • failOpen: true only applies to eligible service-unavailable conditions. It never turns missing, invalid, expired, replayed, mismatch, or blocked into 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_KEY on the server. Never embed it in Svelte components or PUBLIC_* variables.
  • Browser requests use same-origin application endpoints, not direct requests to the upstream TrueSift service.
  • Verify the returned verificationToken in the final business route. UI state must never authorize the operation by itself.
  • Define expectedAction, expectedPath, and expectedOrigin from trusted server-side application context.
  • Never trust browser-provided allowed, decision, score, status, failOpen, challengeId, action, path, or origin as 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.