Data Types

Signed URLs

Share URLs to private files using immutable pre-authenticated URLs — no HTTP headers required.

A Signed URL is a standard Bytescale file URL with special query string parameters appended (exp and sig). These parameters make the URL tamper-proof, time-limited, and pre-authenticated using a specific API key, enabling secure access to private files without the use of HTTP authentication headers.

A Signed URL includes the following query string parameters:

  • exp: A UNIX timestamp (in seconds or milliseconds) defining when the URL should expire.

  • sig: A HMAC-SHA-256 signature that authenticates the URL contents.

Example:

https://upcdn.io/W142hJk/image/uploads/photo.jpg?w=800&exp=1712345678&sig=1.sRta...

When a request is made, Bytescale will:

  1. Parse the exp and sig parameters.

  2. Check that the URL has not expired (using the expiration time inside exp).

  3. Check that the URL has not been modified (using the cryptographic signature inside sig).

  4. Apply the API key’s permissions at the time of the request.

To create a Signed URL:

  1. Construct the full URL including any query string parameters (such as file transformation parameters).

  2. Remove the URL scheme (e.g. https://).

  3. Add an exp parameter specifying when the URL should expire (in Unix epoch seconds or milliseconds).

    This is a required parameter and must be less than 7 days from now.

  4. Generate an HMAC-SHA-256 signature of the full URL, excluding the URL scheme and sig parameter, using your API key’s "Secure URL Key" as the HMAC secret.

    E.g. hmac("upcdn.io/W142hJk/raw/example.jpg?exp=1748204711", decodeBase64(secureUrlKey))

  5. Append the sig parameter to the URL, with the value of <sig-version>.<api-key-id>.<hmac-signature>

    E.g. "upcdn.io/W142hJk/raw/example.jpg?exp=1748204711&sig=1.BMCyGyFk.6Kfhg5wI8t9AOaY1"

    The <sig-version> must always be 1.

    The <hmac-signature> must be Base64Url encoded (not standard Base64).

// Credentials (from: Bytescale Dashboard > Security > API Keys > Edit > Secure URLs)
const apiKeyId = "****dk7S"; // ⬅️️ REPLACE with your API Key ID
const secureUrlKey = "********************AA=="; // ⬅️️ REPLACE with your Secure URL Key
/**
* Converts a regular Bytescale File URL into a signed URL.
*
* @param fileUrl E.g. "https://upcdn.io/W142hJk/image/example.jpg?w=800&h=600"
* Must be a valid URL (i.e. must already be URL encoded)."
* @param ttlSeconds How long (from now) the URL should remain valid (default: 10 minutes).
* @param ttlIncrementSize Rounds expiration to this interval to improve CDN caching (default: 60 seconds).
* @returns Signed Bytescale File URL (with the signature in the 'sig' parameter).
*/
async function getSignedUrl(fileUrl, ttlSeconds = 600, ttlIncrementSize = 60) {
if (!/^(https?:)?\/\//i.test(fileUrl)) {
throw new Error("Invalid URL: must start with http://, https://, or //");
}
if (ttlSeconds < 1 || ttlSeconds > 604800) {
throw new Error("Invalid TTL: must be between 1 second and 7 days.");
}
if (ttlIncrementSize < 1 || ttlIncrementSize > 604800) {
throw new Error("Invalid TTL increment: must be between 1 second and 7 days.");
}
// 1. Create signing key (HMAC SHA-256)
const signingKeyData = Buffer.from(secureUrlKey, "base64"); // Bytescale URL Security Keys are Base64-encoded and decode to a valid AES key length.
const signingKey = await crypto.subtle.importKey("raw", signingKeyData, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
// 2. Calculate rounded expiration time (the 'exp' parameter is mandatory for signed URLs)
const now = Math.floor(Date.now() / 1000);
const expiration = Math.ceil((now + ttlSeconds) / ttlIncrementSize) * ttlIncrementSize;
const expirationParam = `exp=${expiration}`;
const delimiter = fileUrl.includes("?") ? "&" : "?";
const urlWithExpiration = `${fileUrl}${delimiter}${expirationParam}`;
// 3. Prepare schemeless URL string (Bytescale signs without scheme for portability)
const schemelessUrl = urlWithExpiration.slice(urlWithExpiration.indexOf("//") + 2);
const dataToSign = new TextEncoder().encode(schemelessUrl);
// 4. Create signature (HMAC)
const signatureBuffer = await crypto.subtle.sign("HMAC", signingKey, dataToSign);
const signatureBase64Url = Buffer.from(signatureBuffer).toString("base64url");
// 5. Construct 'sig' parameter (format: version.apiKeyId.signature)
const sigParam = `sig=1.${apiKeyId}.${signatureBase64Url}`;
// 6. Return fully signed URL
return `${urlWithExpiration}&${sigParam}`;
}
// Returns: "https://upcdn.io/W142hJk/image/example.jpg?w=800&h=600&exp=...&sig=..."
console.log(
await getSignedUrl("https://upcdn.io/W142hJk/image/example.jpg?w=800&h=600")
);
  • Security:

    • Signed URLs are not encrypted; the path and query parameters remain visible (although they cannot be modified by the user).

    • Permissions are enforced at request time using the associated API key.

    • Revoking the API key immediately invalidates existing URLs (providing they have not already been served and cached).

    • Short expiration times help limit the impact of leaked URLs. See Expiring URLs.

  • Performance:

    • Rounded expiration times help improve CDN cache hits by ensuring URLs for the same resource, generated within a short time window, are identical. This is achieved by aligning their expiration times. The JavaScript implementation above handles this using the ttlIncrementSize parameter.

    • All Signed URLs that reference the same transformed resource share the same permanent cache entry. In other words, the permanent cache does not vary based on the sig or exp parameters.

Was this section helpful? Yes No

You are using an outdated browser.

This website requires a modern web browser -- the latest versions of these browsers are supported: