Skip to main content
POST /v2/storefront/preview-token mints a short-lived signed token that the Template Designer Live Preview iframe uses to render the merchant’s draft storefront. The token is bound to a specific shop and expires after 10 minutes.
Closes Phase 3 of DEV-AUDIT #199. The Template Designer Live Preview surface in the shop-builder dashboard calls this endpoint, embeds the returned previewUrl into an iframe, and the storefront verifier (separate ticket) HMAC-checks the token before rendering the draft.

When to use this

  • The Template Designer’s “Live Preview” CTA — mint a token, embed previewUrl into the iframe src
  • Any merchant-facing tool that needs a time-bounded preview link to a draft storefront
  • Do NOT use this for production publishing — published storefronts don’t need a token

Authentication

Merchant JWT (MerchantOnlyJwtGuard). The endpoint also asserts req.user.shopId === body.shopId at the controller layer — a merchant can only mint preview tokens for their own shop. Cross-shop mint attempts return 403.

Request

Body:

Curl example

Response (200)

Field reference

Token format

<payload-b64url>.<sig-b64url> where:
  • payload — UTF-8 JSON { shopId: string, expiresAt: ISO-8601 string, kind: "storefront-preview" } then base64url-encoded
  • sig — HMAC-SHA-256 of the payload bytes under PREVIEW_TOKEN_SECRET (or JWT_SECRET fallback for dev), then base64url-encoded
The two parts are joined with a . separator. Shopfront verifier:

Why HMAC over JWT

The shopfront verifier is a single shared-secret check at the storefront edge. HMAC avoids:
  • JWKS round-trips (no key fetch from a JWKS endpoint at request time)
  • Asymmetric key rotation surface (no private/public key pair, no kid)
  • Edge-cache invalidation on key rotation
  • The full jose library dependency at the storefront edge
The fixed-shape payload ({shopId, expiresAt, kind}) is also stricter than typical JWT claim taxonomy — iss/aud/jti/sub buy nothing for a 10-minute single-purpose token. If we ever need rotation, audience-narrowing, or a kid, the swap is one file behind PreviewTokenService.mint — the controller response shape does not change.

TTL + rate limiting

  • TTL: 10 minutes from mint. Hard-coded constant PREVIEW_TOKEN_TTL_MS in the service.
  • Rate limit: not applied at v1. The cross-shop guard limits abuse blast radius to “self-DoS your own preview.” Revisit if cross-merchant abuse signal appears.
  • Single chokepoint: all minting goes through PreviewTokenService.mint so audit-log + rotation hooks can be added in one place.

Error responses