Signed URLs
HMAC-SHA256 signed URLs for secure asset access.
Signed URLs
Signed URLs provide time-limited access to assets using HMAC-SHA256 signatures.
Signing alone does not restrict access
A vault is public by default, and a public vault serves its assets to
anyone with the URL. Signing a URL for a public vault does not change that:
the unsigned URL still works, because there is nothing to enforce.
To make signatures mandatory, set the vault to private:
curl -X PATCH https://api.imgbt.com/api/v1/vaults/$VAULT_ID \
-H "Content-Type: application/json" \
-d '{"visibility":"private"}'A private vault rejects every unsigned request with 403, including requests
that carry only transform parameters. Its responses are also sent
Cache-Control: private, no-store and are never written to the shared edge
cache.
How It Works
- Your server generates a signed URL with an expiration time
- The URL includes
tokenandexpiresquery parameters - imgbt verifies the signature at the edge before serving the asset
- Expired or tampered URLs are rejected with a
403response - On a private vault, a missing signature is also rejected with
403
Getting your signing secret
Every vault is created with one. Owners and admins can read it:
curl https://api.imgbt.com/api/v1/vaults/$VAULT_ID/signing-secret
# => {"signing_secret":"..."}It is deliberately not included in the normal vault response, and members cannot read it.
Rotating it
Rotation is how you revoke leaked links — it invalidates every outstanding signed URL for the vault immediately.
curl -X POST https://api.imgbt.com/api/v1/vaults/$VAULT_ID/signing-secret/rotate
# => {"signing_secret":"<new value>"}Generating Signed URLs
Signing Payload
The signing payload is constructed from three components separated by newlines:
{path}\n{sorted_query_params}\n{expires}- path: The URL pathname (e.g.,
/marketing/blog/hero/photo.jpg) - sorted_query_params: Transform parameters sorted alphabetically by key (excluding
tokenandexpires) - expires: Unix timestamp in seconds
Example (Node.js)
import { createHmac } from 'crypto'
function signUrl(url, secret, expiresAt) {
const urlObj = new URL(url)
const path = urlObj.pathname
// Sort query params (excluding token and expires)
const params = new URLSearchParams(urlObj.search)
params.delete('token')
params.delete('expires')
params.sort()
const payload = `${path}\n${params.toString()}\n${expiresAt}`
const signature = createHmac('sha256', secret).update(payload).digest('base64url')
urlObj.searchParams.set('expires', String(expiresAt))
urlObj.searchParams.set('token', signature)
return urlObj.toString()
}
// Sign a URL that expires in 1 hour
const expiresAt = Math.floor(Date.now() / 1000) + 3600
const signed = signUrl(
'https://cdn.example.com/photos/album/main/photo.jpg?w=800&format=webp',
'your-vault-signing-secret',
expiresAt,
)Vault Signing Secret
The signing secret is stored in your vault settings. You can view it in the dashboard under Vault Settings. Keep this secret secure — anyone with the secret can generate valid signed URLs.
Caching Behavior
Signed URLs bypass the edge cache to prevent unauthorized access via cached responses. Each request is verified independently.