Skip to main content
A Theme in signed mode requires every serving URL to carry a signature. This page is the whole specification — enough to reimplement in any language.
Signing is opt-in. A new Theme is public, and a public Theme ignores a signature if one is present. Turn signing on in the portal, on the Theme’s Settings tab.

Keys

A Theme holds a set of signing keys. Each key is a pair:
The secret never leaves your server. Livry does not return it from any API at any access level — the API returns key ids only. If you lose it, create a new key and delete the old one.A signature computed in the browser means shipping the secret to every user, which defeats the entire mechanism.
A Theme can hold several keys at once, which is what makes rotation possible without downtime.

The algorithm

Where path is the URL path from the leading slash, with no host and no query string:

Three details that matter

?kid= is part of the canonical string, not just of the URL. A signature made with one key therefore cannot be replayed against another key’s id on the same path.
A URL with a doubled slash, a percent-encoded letter or a trailing dot is normalised before the signature is checked. You cannot sign one spelling and serve another.
No host, so the same signature keeps working if cdn.livry.dev moves behind a CDN.No expiry, deliberately. An expiring signature changes the URL on every mint, which would defeat an edge cache entirely — and caching is the whole premise of the serving path. Deleting a key is the revocation mechanism; see below.

Worked example

base64url with no padding. - and _ rather than + and /, and trailing = stripped. Node’s "base64url" and .NET’s Base64Url do this for you; Python and shell need the transform shown above.

Where to sign

Because there is no expiry, a signed URL is stable — you can compute it once and cache it alongside anything else.

Verification, and what a failure looks like

A signed Theme answers 403 when the signature is missing, malformed, made with an unknown key, or simply wrong. It is one answer for all four, so a caller probing keys learns nothing about which key ids exist. The comparison is constant-time, over the decoded bytes.

Rotation

A Theme holds several keys at once, so rotation has no cutover:
1

Add a second key

Both are now valid. URLs signed with either are served.
2

Deploy your app signing with the new key

Existing URLs signed with the old one keep working.
3

Delete the old key

URLs signed with it start answering 403.
Deleting a key is the only revocation, and caches lag it. A signature has no expiry, so a URL signed with a deleted key can keep working from a cache that already held it.That window is bounded: a signed answer is never cached for more than 300 seconds, whatever your Environment’s cache lifetime says. Wait five minutes after deleting a key before assuming it is dead everywhere.

Signing is not an allowlist

A signature proves the URL was minted by someone holding your secret. It says nothing about where the request came from — anyone who obtains a signed URL can fetch it. If you also need to constrain who may fetch, that is a separate mechanism:

Access control

Origin allowlists, IP ranges, and what each one is actually worth.