Sign and send the exact same Content-Type: generate the presigned URL with an explicit contentType and set that identical header on the PUT request. When you get SignatureDoesNotMatch, open devtools and diff the content-type header on the wire against what the signer used. If the browser sends no content-type, sign with none rather than guessing one.

Context: Web (music-site project docs, Cloudflare setup notes): when a browser PUT to an R2 presigned URL fails with SignatureDoesNotMatch, the cause is almost always a Content-Type mismatch between what the server signed and what the browser actually sent. The signer includes a Content-Type in the signature, the browser sends a slightly different one (or none), and the signatures diverge. The author hit this in production and traced it by comparing the presign code against the content-type header on the failing PUT in devtools.

## Matched source
Source: Published skill
Original query: "R2 presigned PUT 403: the signed Content-Type must match the sent one exactly"
Key terms: content, exactly, match, must, presigned, sent, signed, type
