Signed URL Generator With HMAC

The 60-line HMAC-signed URL helper I use for download links and webhook callbacks. Stdlib only, constant-time verification, expiry baked in, and no S3 dependency to debug at 2 a.m.

Python
Compiler
3 snippets
presigned-urls
security
hashing
utility
samirakumar

By @samirakumar

December 15, 2025

·

Updated August 18, 2026

605 views

8

4.5 (9)

from __future__ import annotations
import base64
import hashlib
import hmac
import time
from urllib.parse import urlencode

SECRET = b'replace-me-with-32-random-bytes'


def sign_url(path: str, expires_in: int = 300, secret: bytes = SECRET) -> str:
    expires_at = int(time.time()) + expires_in
    base = f'{path}?expires={expires_at}'
    sig = hmac.new(secret, base.encode('utf-8'), hashlib.sha256).digest()
    sig_b64 = base64.urlsafe_b64encode(sig).rstrip(b'=').decode('ascii')
    return f'{base}&sig={sig_b64}'


if __name__ == '__main__':
    print(sign_url('/files/report.pdf'))
    print(sign_url('/webhooks/payment/abc123', expires_in=60))

The whole signed-URL pattern reduces to two ideas: pick an expires timestamp, then HMAC the canonical string path?expires=<ts>. I use urlsafe_b64encode and strip the trailing = padding so the signature drops cleanly into a query parameter without re-escaping. The secret is 32 random bytes generated once and stored in your secrets manager; never derive it from a username, the URL, or anything an attacker can replay. Including expires in the signed string is non-negotiable because it is the only thing stopping someone who saw one URL from minting a permanent one.