Skip to content

Content Protection

How Sesamy protects premium content on your pages. Capsule encryption is the recommended approach, but several alternatives are available for different use cases.

Capsule uses AES-256-GCM encryption to protect content at rest. The encrypted content is embedded directly in the HTML -- even if someone views the page source, they see only ciphertext. Keys are fetched from the server only after entitlement is verified.

How it works

  1. At publish time, your CMS encrypts the premium content and embeds it as a DCA (Digital Content Access) manifest
  2. sesamy-js detects the encrypted content on page load
  3. If the reader is authenticated and entitled, sesamy-js fetches the decryption key and injects the decrypted HTML
  4. If the reader lacks access, Sesamy injects a paywall instead

DCA manifest structure

The manifest produced by @sesamy/capsule-server (format 0.10) is a single <script class="dca-manifest"> element. The ciphertext lives inside the manifest JSON -- there is no separate <template> element.

html
<script class="dca-manifest" type="application/json">
  {
    "version": "0.10",
    "resourceJWT": "eyJ...",
    "content": {
      "bodytext": {
        "contentType": "text/html",
        "iv": "base64url-iv...",
        "aad": "scope:premium",
        "ciphertext": "base64url-ciphertext...",
        "wrappedContentKey": [
          { "kid": "260423T10", "iv": "...", "ciphertext": "..." }
        ]
      }
    },
    "issuers": {
      "sesamy": {
        "unlockUrl": "https://api2.sesamy.com/capsule/vendors/YOUR_VENDOR_ID/unlock",
        "keys": [
          {
            "contentName": "bodytext",
            "scope": "premium",
            "contentKey": "...",
            "wrapKeys": [{ "kid": "260423T10", "key": "..." }]
          }
        ]
      }
    }
  }
</script>

sesamy-js also accepts the legacy shape (resource, issuerData) produced by older integrations -- see content/index.ts for the compatibility fallbacks.

The resourceJWT

The resourceJWT inside the DCA manifest carries all content metadata: content ID, pass/tier, URL, price, currency, and more. This means you do not need separate <meta> tags for Sesamy content metadata when using Capsule -- the JWT is the single source of truth.

sesamy-js reads the resourceJWT automatically and uses it for entitlement checks, paywall configuration, and analytics. If you need to verify the resourceJWT on your own backend, see Token Validation.

Migrating from meta tags

During the transition, sesamy-js reads metadata from the resourceJWT first and falls back to meta tags when no DCA manifest is present. You can run both systems side by side. See the migration guide for details.

Configuration

Enable Capsule in your sesamy-js config:

json
{
  "clientId": "your-vendor-id",
  "vendorId": "your-vendor-id",
  "capsule": { "enabled": true }
}

sesamy-js will automatically:

  • Detect <script class="dca-manifest"> elements on the page
  • Authenticate the user and call the unlock endpoint
  • Decrypt all content items
  • Inject the decrypted HTML into the page

Benefits

  • Content is encrypted at rest -- viewing page source reveals nothing
  • Works with static sites and CDNs -- no dynamic rendering required
  • Share links supported via pre-signed tokens
  • Cache-friendly -- encrypted pages can be cached indefinitely
  • Automatic paywall injection when the reader lacks access

Client-side rendering

With Capsule, you can cache everything hard because the encrypted HTML is the same for every reader. The decryption and paywall display happen entirely client-side. This is the recommended rendering approach for most publishers.

Locking is handled by Sesamy

Because Sesamy controls the unlock decision, you get access to features like paywall strategies (A/B testing different paywalls for different readers) and leaky paywall (granting free access to a set number of articles). These features work transparently with Capsule -- no extra code needed.


Alternative Lock Modes

These lock modes use the <sesamy-content-container> web component instead of Capsule. They are still supported and useful for specific scenarios.

Proxy Lock Mode

Content is stored on the server and fetched only after the user is authenticated and entitled. The page contains only a preview; the full content is never in the HTML source.

html
<sesamy-content-container
  item-src="https://example.com/articles/premium-article"
  publisher-content-id="article-123"
  lock-mode="proxy"
>
  <div slot="preview">
    <p>This is a preview of the premium content...</p>
  </div>
</sesamy-content-container>

Good security, but requires the content to be available via URL for the proxy to fetch. Not suitable for fully static sites.

Encode Lock Mode

Content is base64-encoded in the page source and decoded client-side after authentication. Prevents casual copy-paste but is not cryptographically secure -- anyone who reads the page source can decode the content.

html
<sesamy-content-container
  item-src="https://example.com/articles/encoded-article"
  publisher-content-id="article-456"
  lock-mode="encode"
>
  <div slot="preview"><p>Free preview...</p></div>
</sesamy-content-container>

Embed Lock Mode (CSS Hidden)

Content is present in the DOM but hidden with CSS until the user is authenticated. The least secure option -- the content is fully visible in the page source.

html
<sesamy-content-container
  publisher-content-id="article-789"
  lock-mode="embed"
>
  <div slot="preview"><p>Read the first paragraph for free...</p></div>
  <div slot="locked"><p>This content is hidden with CSS until unlocked.</p></div>
</sesamy-content-container>

Event Lock Mode

No content is managed by Sesamy. A sesamyUnlocked event fires when the user has access, and you handle the content display yourself.

html
<sesamy-content-container
  publisher-content-id="event-101"
  lock-mode="event"
>
  <div slot="preview"><p>Subscribe to unlock this content.</p></div>
</sesamy-content-container>

<script>
  document.addEventListener('sesamyUnlocked', (event) => {
    const { publisherContentId } = event.detail;
    fetchAndRenderContent(publisherContentId);
  });
</script>

Fetch from API

For fully custom implementations, skip the web components and use the sesamy-js API directly:

javascript
window.addEventListener('sesamyJsReady', async () => {
  const hasAccess = await window.sesamy.entitlements.hasAccess('premium-article-123');

  if (hasAccess) {
    document.querySelector('.premium-content').style.display = 'block';
  } else {
    document.querySelector('.paywall').style.display = 'block';
  }
});

Comparison

StrategySecurityContent in source?CDN-friendlyComplexity
Capsule (DCA)ExcellentEncrypted onlyYesLow (auto-processed)
ProxyGoodNoNo (dynamic fetch)Low
EncodeLowEncoded (reversible)YesLow
Embed (CSS)NoneYes (plain text)YesLowest
EventDepends on impl.NoDependsMedium
API fetchDepends on impl.NoDependsHighest

Next Steps

Released under the MIT License.