Skip to main content

Security & Rate Limiting

The SDK is designed so that theme and app developers get safe defaults for free. This page explains what protects each request, where the limits are, and the rules for using the SDK responsibly.

How a request is protected

Every call to /api/sdk/* is validated server-side before your handler runs. The SDK attaches the required headers automatically; you never assemble them yourself.

ProtectionHow it works
CSRF tokenX-SITEPACK-CSRF must match the server-issued sitepack_sdk token for the page.
Site bindingX-SITEPACK-SITE-UUID must match the site the request is served from.
Same-origin sessionRequests are sent with credentials: 'include'; the customer's session cookie identifies them.
InitiatorX-SITEPACK-INITIATOR-TYPE (theme/app) and -ID attribute the call to your theme or app.
Rate limitingPer-IP limiter on the server, plus a client-side token bucket (see below).
The client-id header is a binding check, not a secret

X-SITEPACK-CLIENT-ID is a hash derived from the visitor's request context. It ties a call to the page it was issued for — but it is not a secret and provides no authentication on its own. The real guarantees come from the CSRF token, the same-origin session cookie, and the site UUID. Never treat the client-id as a credential.

The two rate-limit layers

1. Client-side token bucket

The SDK shares one token bucket across all instances on the page: 15 tokens, refilling fully over 10 seconds. Each request spends one token. When the bucket is empty, the call is rejected locally — before any network request — with:

Too Many Requests (Client-side Rate Limit)

Concurrency is also capped: at most 2 requests are in flight at once; the rest queue.

2. Server-side per-IP limiter

Independently, the server enforces a per-IP limit on /api/sdk/* (15 requests / 10 seconds). Exceeding it returns HTTP 429, which the SDK surfaces as a rejected promise with a "Too many requests" message.

Stricter limits on sensitive endpoints

Some endpoints add their own throttle on top of the generic limiter:

EndpointExtra limit
account.login, account.resetPasswordStrict per-IP and per-email throttle (anti credential-stuffing).
contactSiteOwner5 submissions / hour per visitor, plus honeypot spam detection.

Handling 429 gracefully

try {
await sdk.search.query(term);
} catch (err) {
if (err.message.includes('Too Many Requests') || err.message.includes('Too many requests')) {
sdk.ui.notify('Please slow down for a moment.', 'warning');
return;
}
throw err;
}

Safe-usage rules

Do
  • Handle every promise rejection — network errors, validation errors, 401, and 429 all surface as rejections.
  • Debounce and batch — debounce search-as-you-type, batch UI updates, reuse the cached cart.
  • Let the SDK cachegetCart, checkOrderCapability, and order-capability lookups are cached; don't force-refresh unnecessarily.
  • Read the Account rules before touching login.
Don't
  • Don't hardcode secrets (API keys, tokens, passwords) in theme or app JavaScript — it ships to the browser and is fully visible.
  • Don't overwrite the SITEPACK_* globals — the SDK relies on them for request signing and will throw if they're missing.
  • Don't store passwords or personal data in cookies or localStorage — keep them on the server session.
  • Don't poll in tight loops or fire speculative requests — you'll trip the rate limiter for the whole page.
  • Don't rely on the client-side rate limiter for security — it's a UX smoother; the server is the real boundary.