JWT Offline Verification

How Slootly eliminates API latency with signed subscription tokens.

Overview

Slootly uses signed JWT (JSON Web Token) tokens to verify subscription status without making an API call on every bot interaction. When the SDK calls the API, the response includes a signed JWT containing the subscription state. On subsequent checks, the SDK verifies the token locally, achieving sub-millisecond latency.

This means Slootly is not a runtime dependency for your bot. Even if Slootly's API is temporarily unreachable, cached JWTs continue to work until they expire.

How It Works

  1. Your bot calls gate.check(userId, platform)
  2. The SDK checks its in-memory cache. If a valid entry exists, it returns immediately.
  3. If no cache hit, the SDK checks for a cached JWT token and verifies it using Ed25519 public keys from Slootly's JWKS endpoint.
  4. If no valid JWT, the SDK calls the Slootly API, which returns the subscription state along with a fresh JWT.
  5. The SDK caches both the subscription object and the JWT for future checks.
  6. Background refresh is triggered when the cache entry passes its half-life, ensuring fresh data without blocking.

Token Structure

Slootly JWTs are signed with Ed25519 (EdDSA algorithm). The payload contains the full subscription state:

json
{
  "sub": "sub_abc123",         // subscriber ID
  "plan": "pro",               // plan slug
  "plan_id": "plan_xyz789",    // plan ID
  "status": "active",          // subscription status
  "current_period_start": "2026-03-01T00:00:00Z",
  "current_period_end": "2026-04-01T00:00:00Z",
  "cancel_at_period_end": false,
  "trial_end": null,
  "platforms": ["telegram", "discord"],
  "metadata": {},
  "project_id": "proj_abc",
  "platform": "telegram",
  "platform_user_id": "123456789",
  "iat": 1711036800,          // issued at (Unix timestamp)
  "exp": 1711040400,          // expires (Unix timestamp)
  "iss": "https://api.slootly.dev",
  "kid": "key_001"            // key ID for JWKS lookup
}

Key Rotation

Slootly rotates signing keys periodically. The JWKS endpoint always contains the current and previous signing keys to ensure a smooth transition. The SDK handles key rotation automatically:

  • The SDK fetches JWKS keys on first use and caches them
  • If a JWT fails verification with cached keys, the SDK re-fetches JWKS before giving up
  • The kid (key ID) field in the JWT header identifies which key was used for signing

Cache TTL Configuration

The cache TTL controls how long subscription data is cached before the SDK attempts a refresh. This is a trade-off between latency and freshness.

SettingValueBehavior
Default TTL5 minutes (300s)Good balance for most bots
Aggressive1 minute (60s)Near-real-time updates, more API calls
Relaxed15 minutes (900s)Fewer API calls, longer staleness window
typescript
// TypeScript: Set 2-minute cache TTL
const gate = new Slootly({
  apiKey: 'sk_live_...',
  cacheTtlMs: 120_000,  // 2 minutes
});

# Python: Set 2-minute cache TTL
gate = Slootly("sk_live_...", cache_ttl=120)  # 2 minutes

Tip

The SDK uses a half-life refresh strategy. When a cache entry is past 50% of its TTL, the SDK triggers a background refresh while still serving the cached value. This means most subscription checks return instantly from cache with no blocking API calls.

Handling Staleness

There is an inherent delay between a subscription change (e.g., new payment or cancellation) and the SDK reflecting that change. This delay equals the cache TTL at most.

For most bots, this is acceptable. A 5-minute delay between payment and feature access is fine. If you need instant updates, use webhooks:

typescript
// Clear cache on webhook event for instant update
gate.onEvent((event) => {
  if (event.type === 'subscription.created') {
    gate.clearCache();  // Next check() will fetch fresh data
  }
});

Disabling JWT Verification

You can disable JWT verification if you prefer API-only checks. This means every check() call hits the Slootly API (after cache expires). Not recommended for high-traffic bots.

typescript
// TypeScript
const gate = new Slootly({
  apiKey: 'sk_live_...',
  enableJwtVerification: false,
});

# Python
gate = Slootly("sk_live_...", offline_verification=False)

Security Considerations

  • Ed25519 signatures are used for JWT signing. Ed25519 provides high security with fast verification times.
  • Tokens are short-lived. JWT expiration matches the cache TTL. Expired tokens are never accepted.
  • JWKS keys are cached. The SDK caches public keys and re-fetches them only when verification fails, preventing unnecessary network calls.
  • Tokens are server-side only. JWTs should never be exposed to end users. They are used internally by the SDK running in your bot's server environment.