Screenshot -- Signed URLs
Share API access securely without exposing your API key using HMAC-SHA256 signed URLs.
Base URL: https://api.nodium.io/api/v1/screenshot
Table of Contents
- The <code class="rounded bg-muted px-1.5 py-0.5 text-sm">signature</code> Parameter
- Time-Limited Signatures
- Enforcing Signed URLs
- Code Examples
Why Signed URLs?
In many applications, you need to expose screenshot URLs on the client side -- for example, in <img> tags, iframes, or public-facing pages. Using your raw API key in these URLs is a security risk because:
- Anyone who views the page source can extract your API key
- The key could be used to make unauthorized requests against your quota
- API keys in URLs may be logged by proxies, CDNs, or analytics tools
Signed URLs solve this problem. They allow you to pre-authorize specific screenshot requests without revealing your API key. The recipient can use the signed URL to capture a screenshot, but they cannot modify the parameters or use it for other requests.
Use cases include:
- Client-side
<img>tags that request screenshots on load - Public embeds on third-party websites
- Shareable links that capture a specific page with specific settings
- Webhook integrations where a third party triggers screenshots
How It Works
Signed URLs use HMAC-SHA256 to create a tamper-proof signature of all request parameters. The signature is generated using your signing key (different from your API key) and appended to the URL.
1. You construct the screenshot URL with all desired parameters
|
v
2. You sort the parameters alphabetically and concatenate them
|
v
3. You compute HMAC-SHA256(signing_key, canonical_string)
|
v
4. You append the hex-encoded signature as the `signature` parameter
|
v
5. The signed URL can be used publicly without exposing your API keyWhen the API receives a signed request, it:
- Extracts all parameters except
signature - Recomputes the signature using your signing key
- Compares the computed signature with the provided one
- Rejects the request if they do not match
Creating a Signed URL
Step-by-Step Algorithm
1. Gather your parameters
Start with all the query parameters you want in the URL, including access_key:
access_key = YOUR_API_KEY
url = https://example.com
format = png
viewport_width = 12802. Sort parameters alphabetically by key
access_key = YOUR_API_KEY
format = png
url = https://example.com
viewport_width = 12803. Concatenate into a canonical string
Join all key-value pairs with &:
access_key=YOUR_API_KEY&format=png&url=https://example.com&viewport_width=12804. Compute the HMAC-SHA256 signature
Using your signing key (available in the Nodium Dashboard under Settings > Signing Key):
HMAC-SHA256(signing_key, "access_key=YOUR_API_KEY&format=png&url=https://example.com&viewport_width=1280")5. Append the signature
Add the hex-encoded signature as the signature parameter:
https://api.nodium.io/api/v1/screenshot/take?access_key=YOUR_API_KEY&format=png&url=https://example.com&viewport_width=1280&signature=a1b2c3d4e5f6789012345678abcdef0123456789abcdef0123456789abcdef01Important: The signature parameter is excluded from the canonical string used to compute the hash. Only include it in the final URL.
The signature Parameter
| Parameter | Type | Description |
|---|---|---|
signature | string | Hex-encoded HMAC-SHA256 signature of the canonical parameter string, computed with your signing key. |
The signature validates that:
- The parameters have not been modified since signing
- The request was authorized by someone with access to the signing key
- The URL is intended for this specific set of parameters
Time-Limited Signatures
Add an expires parameter to create signatures that are only valid until a specific time. This prevents signed URLs from being reused indefinitely.
access_key=YOUR_API_KEY&expires=1709654400&format=png&url=https://example.comThe expires value is a UNIX timestamp (seconds since epoch). The API rejects requests where the current time exceeds the expires value.
| Parameter | Type | Description | Example |
|---|---|---|---|
expires | integer (UNIX timestamp) | Expiration time for the signed URL. Requests after this time are rejected. | 1709654400 |
// Create a signed URL that expires in 1 hour
const expires = Math.floor(Date.now() / 1000) + 3600;Recommendation: Always use expires for signed URLs that will be exposed on public pages. A TTL of 1--24 hours is appropriate for most use cases.
Enforcing Signed URLs
You can make signed URLs mandatory for your organization in the Nodium Dashboard under Settings > Signed URLs. When enforcement is enabled:
- All requests without a valid
signatureparameter are rejected with error codesignature_is_required - All requests with an invalid signature are rejected with error code
signature_is_not_valid - This applies to all endpoints (
/take,/animate,/bulk)
This is recommended for production environments where you want to ensure that all API usage is pre-authorized.
Code Examples
Node.js
const crypto = require("crypto");
const API_KEY = "YOUR_API_KEY";
const SIGNING_KEY = "YOUR_SIGNING_KEY";
function createSignedUrl(params) {
// 1. Add access_key to params
const allParams = { access_key: API_KEY, ...params };
// 2. Sort parameters alphabetically
const sortedKeys = Object.keys(allParams).sort();
// 3. Build canonical string
const canonicalString = sortedKeys
.map((key) => `${key}=${allParams[key]}`)
.join("&");
// 4. Compute HMAC-SHA256 signature
const signature = crypto
.createHmac("sha256", SIGNING_KEY)
.update(canonicalString)
.digest("hex");
// 5. Build the final URL
const queryString = canonicalString + `&signature=${signature}`;
return `https://api.nodium.io/api/v1/screenshot/take?${queryString}`;
}
// Usage
const signedUrl = createSignedUrl({
url: "https://example.com",
format: "png",
viewport_width: 1280,
viewport_height: 720,
});
console.log(signedUrl);
// Use in HTML: <img src="${signedUrl}" />With expiration:
function createTimeLimitedSignedUrl(params, ttlSeconds = 3600) {
const expires = Math.floor(Date.now() / 1000) + ttlSeconds;
return createSignedUrl({ ...params, expires });
}
// URL valid for 1 hour
const signedUrl = createTimeLimitedSignedUrl({
url: "https://example.com",
format: "png",
});Python
import hmac
import hashlib
import time
from urllib.parse import urlencode
API_KEY = "YOUR_API_KEY"
SIGNING_KEY = "YOUR_SIGNING_KEY"
def create_signed_url(params: dict) -> str:
# 1. Add access_key to params
all_params = {"access_key": API_KEY, **params}
# 2. Sort parameters alphabetically
sorted_params = sorted(all_params.items())
# 3. Build canonical string
canonical_string = "&".join(f"{k}={v}" for k, v in sorted_params)
# 4. Compute HMAC-SHA256 signature
signature = hmac.new(
SIGNING_KEY.encode(),
canonical_string.encode(),
hashlib.sha256,
).hexdigest()
# 5. Build the final URL
query_string = canonical_string + f"&signature={signature}"
return f"https://api.nodium.io/api/v1/screenshot/take?{query_string}"
# Usage
signed_url = create_signed_url({
"url": "https://example.com",
"format": "png",
"viewport_width": 1280,
"viewport_height": 720,
})
print(signed_url)
# With expiration
def create_time_limited_signed_url(params: dict, ttl_seconds: int = 3600) -> str:
expires = int(time.time()) + ttl_seconds
return create_signed_url({**params, "expires": expires})
# URL valid for 1 hour
signed_url = create_time_limited_signed_url({
"url": "https://example.com",
"format": "png",
})PHP
<?php
$apiKey = 'YOUR_API_KEY';
$signingKey = 'YOUR_SIGNING_KEY';
function createSignedUrl(array $params, string $apiKey, string $signingKey): string {
// 1. Add access_key to params
$params['access_key'] = $apiKey;
// 2. Sort parameters alphabetically
ksort($params);
// 3. Build canonical string
$canonicalString = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
// 4. Compute HMAC-SHA256 signature
$signature = hash_hmac('sha256', $canonicalString, $signingKey);
// 5. Build the final URL
return "https://api.nodium.io/api/v1/screenshot/take?{$canonicalString}&signature={$signature}";
}
// Usage
$signedUrl = createSignedUrl([
'url' => 'https://example.com',
'format' => 'png',
'viewport_width' => 1280,
'viewport_height' => 720,
], $apiKey, $signingKey);
echo $signedUrl;
// With expiration
function createTimeLimitedSignedUrl(
array $params,
string $apiKey,
string $signingKey,
int $ttlSeconds = 3600
): string {
$params['expires'] = time() + $ttlSeconds;
return createSignedUrl($params, $apiKey, $signingKey);
}
// URL valid for 1 hour
$signedUrl = createTimeLimitedSignedUrl([
'url' => 'https://example.com',
'format' => 'png',
], $apiKey, $signingKey);Security Best Practices
- Never expose your signing key on the client side. Signature generation must always happen server-side.
- Always use
expiresfor signed URLs on public pages. Without expiration, a signed URL remains valid indefinitely. - Keep TTLs short. A 1-hour expiration is sufficient for most use cases. Use 24 hours at most for URLs that need to persist across sessions.
- Use a different signing key than your API key. The signing key is a separate credential available in the Dashboard. Rotate it periodically.
- Enable enforcement in the Dashboard for production environments. This ensures that unsigned requests are rejected, even if someone obtains your API key.
- URL-encode parameter values consistently. The canonical string must match exactly between your signing code and the API's verification. Use the same encoding on both sides.
- Do not include the
signatureparameter in the canonical string when computing the hash. Onlysignatureis excluded; all other parameters (includingaccess_keyandexpires) are included. - Log and monitor signed URL usage. If you suspect a signing key has been compromised, rotate it immediately in the Dashboard.
- Avoid putting sensitive data in parameters. Even though the URL is signed, the parameters are visible in the URL. Do not include passwords, tokens, or other secrets as parameter values.