Signed URLs

Signed URLs let you authorize proxy traffic without exposing source origins to an allowlist. The signing secret stays on your backend; the URL carries an HMAC + expiry.

When to use signed mode

  • You sign URLs server-side and embed them per-request (most apps).
  • Short-lived URLs (e.g. paywalled content, A/B variants).
  • You want to proxy sources from origins you don't fully control.

If your sources live on a small set of fixed domains, allowlist mode is simpler — skip this guide.

Enable signing

bash
curl -X PUT https://motioness.com/api/projects/$PROJECT_ID/proxy-config \ -H 'Content-Type: application/json' -b cookie.txt \ -d '{ "enable_signing": true }'

This mints a signing secret on first save. Read it from Project → Settings → Proxy → "Signing secret" — it's shown once. Store it as MOTIONESS_SIGNING_SECRET in your backend env.

To rotate it later:

bash
curl -X POST "https://motioness.com/api/projects/$PROJECT_ID/keys/rotate?which=signing" \ -b cookie.txt

URL shape

text
https://motioness.com/v1/{projectKey}/{base64url(srcUrl)}.{format} ?sig={hmacSha256Hex}&exp={unixSeconds}
ComponentNotes
base64url(srcUrl)URL-safe base64 of the full source URL with scheme. Strip trailing = padding.
sigHex-encoded HMAC-SHA256 of ${projectKey}:${srcUrl}:${exp} keyed with the signing secret.
expUnix seconds at which the URL expires. Server rejects with 403 once the clock passes this.

Recipes

ts
import crypto from 'node:crypto';import { buildProxyUrl } from '@motioness/proxy-client';function base64UrlEncode(s: string) { return Buffer.from(s).toString('base64url');}function sign(secret: string, data: string) { return crypto.createHmac('sha256', secret).update(data).digest('hex');}export function motionessSignedUrl(srcUrl: string, ttlSeconds = 3600) { const projectKey = process.env.MOTIONESS_PROJECT_KEY!; const secret = process.env.MOTIONESS_SIGNING_SECRET!; const exp = Math.floor(Date.now() / 1000) + ttlSeconds; const sig = sign(secret, `${projectKey}:${srcUrl}:${exp}`); return buildProxyUrl({ origin: 'https://motioness.com', projectKey, src: srcUrl, signature: sig, exp, });}

Verifying client-side

The browser doesn't verify anything — it just hits the URL. The server verifies. Don't put the signing secret in client-side code.

If you embed signed URLs in HTML returned from your CDN, set a short exp and let the cache expire naturally. For pages that update frequently, sign at request time in your edge function or backend.

Common mistakes

Signed mode requires the src URL to be encoded. Allowlist mode uses plain text. Mixing them returns 403.

The signature MUST be over ${projectKey}:${srcUrl}:${exp} exactly. Different separator? Forgot to include exp? URL-encoded srcUrl? Server returns 403 with no detail (security).

To debug, log the exact string you sign and the exact exp; compare against what the worker reconstructs.

The server rejects when now > exp. If your backend's clock is ahead of the worker's, you might over-shorten the TTL. Stick to >= 60s.

+ and / characters break URL parsing. Use base64url (Buffer.toString('base64url') in Node, RawURLEncoding in Go, etc.) and strip = padding.

Ask a question... ⌘I