SignatureDoesNotMatch

API Auth Signature Mismatch – Fix for AWS SigV4 Errors

Cybersecurity & Malware Intermediate 👁 12 views 📅 Jun 18, 2026

You're calling an AWS API and getting a 403 error saying the signature doesn't match. It usually means your request headers or payload is being altered after signing.

When This Error Hits

You're building an app that talks to AWS — maybe S3, DynamoDB, or Lambda — and you fire off a signed request. Instead of getting back data, you get a 403 with SignatureDoesNotMatch. The error message looks something like:

<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>

This usually happens during development, after you switch from AWS CLI (which handles signing automatically) to raw HTTP calls with your own code. Or when you copy a signed URL from one region to another and forget to update the endpoint. It's also common when you're using a proxy or load balancer that messes with your request headers — like adding or removing a Content-Type or Host header.

What's Actually Going Wrong

SigV4 signing is picky. You sign a specific version of your request — including the HTTP method, the URI path, query strings, headers, and the body. The AWS service then takes your request and re-computes the signature using the same algorithm. If anything — and I mean anything — is different, the signatures won't match and you get the error.

The most common culprits are:

  • Timestamp drift — Your system clock is off by more than 15 minutes. AWS uses the x-amz-date header (or Date header) and compares it to server time. If they differ by over 15 minutes, the signature fails.
  • Wrong signing key — You're using the wrong secret access key, or you're signing with a temporary credential that's expired.
  • Missing or extra headers — You signed a set of headers, but when AWS receives the request, an intermediate proxy added or removed a header. The signed headers list must match exactly.
  • Payload encoding mismatch — You computed the hash of the body, but your HTTP library re-encodes the request body (like adding a charset to JSON).
  • Path or query string changes — A trailing slash that wasn't in the signed request, or query parameters reordered.

The real fix is to compare the canonical request your code built against what AWS expects. I'll show you how.

Fix It Step by Step

These steps assume you're using AWS SigV4 (the default for most AWS APIs). If you're using a different auth scheme, the logic is similar — just adapt the header checks.

  1. Check your system time

    Run this command (Linux or macOS):

    date -u

    On Windows, use:

    w32tm /query /status

    Compare the output to the current UTC time from a trusted source like time.is. If you're off by more than 30 seconds, sync your clock. On Linux, run sudo ntpdate pool.ntp.org or enable NTP. On Windows, go to Date & Time settings and toggle Set time automatically.

    After syncing, wait a minute and retry your API call. If it works, that was the problem. If not, move on.

  2. Verify your credentials

    Double-check your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. If you're using temporary credentials from STS, make sure they haven't expired — they typically last 15 minutes to 36 hours depending on the role. Print them from your code and compare to what's in the AWS console.

    Also check the region. The signing region must match the service endpoint. For example, if you're calling S3 in us-east-1, your credentialScope must say us-east-1/s3/aws4_request. If you accidentally use us-west-2, the signature fails.

  3. Log the canonical request

    This is where you'll catch 90% of mismatches. In your code, before you send the request, print or log the canonical request string — that's the string you sign. For AWS SigV4, it looks like:

    POST
    /
    
    content-type:application/json
    host:s3.us-east-1.amazonaws.com
    x-amz-date:20250315T120000Z
    
    content-type;host;x-amz-date
    hash_of_body

    Now, make the same API call using the AWS CLI or SDK with debug logging turned on. For the CLI:

    aws s3api list-buckets --debug 2>&1 | grep -i canonical

    Or for Python SDK, set boto3.set_stream_logger(''). Compare the canonical request from your code to the one AWS expects. Look closely at:

    • Line endings (must be , no spaces)
    • Header order (must be sorted lowercase)
    • Trailing slashes in the path
    • Query string parameters (must be URL-encoded and sorted)
    • Body hash (SHA-256 of the raw body, not pretty-printed JSON)
  4. Fix header issues

    Common gotcha: your code signs the Host header and x-amz-date, but a proxy adds or removes a header like Accept-Encoding. If the signed headers list includes host;x-amz-date but the received request has Accept-Encoding present, AWS doesn't care — it only cares that the headers you signed are present and match. But if your code signs all headers (using SignedHeaders=host;x-amz-date;accept-encoding) and the proxy removes Accept-Encoding, then the signature fails.

    The fix: only sign the minimum required headers: host and x-amz-date. That's it. Don't sign Content-Type or Content-Length unless you have to (some services require them). Read the service's documentation.

  5. Check the payload hash

    If your request has a body, you must include the SHA-256 hash of the body in the canonical request. Common mistake: you compute the hash of the JSON string, but then your HTTP library re-serializes it (adding or removing spaces). Make sure the body you send over the wire is exactly the same string you hashed. For binary payloads like S3 uploads, use the raw bytes.

    A good test: save the exact body bytes to a file and run sha256sum file.bin (Linux) or certutil -hashfile file.bin SHA256 (Windows). Compare that hash to the one in your canonical request. They must match.

  6. Use the AWS SigV4 test suite

    If you're implementing signing from scratch, don't guess. AWS provides a set of test vectors. Download the SigV4 test suite. Write a small script that runs your signing function against those test cases. If your output matches the expected canonical request and signature, your algorithm is correct. If not, you'll see exactly where it goes off.

If It Still Fails

You've checked time, credentials, headers, and payload hash. Still getting the error? Here are the less common but real possibilities:

  • Proxy or CDN modifies headers — If you're behind CloudFront, an API gateway, or a corporate proxy, it may strip or add headers. Test by sending the request directly to the AWS endpoint (bypass the proxy) using curl from a machine outside your network.
  • URL encoding mismatch — AWS expects query parameter values to be encoded with percent-encoding, and the signing algorithm uses a specific set of characters to encode. For example, a space must be %20, not +. Check your URL encoding library. I've seen Python's urllib.parse.quote vs requests produce different results.
  • Empty body handling — For GET requests, the body hash should be the hash of an empty string (e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855). Some implementations mistakenly skip the body hash entirely.
  • Region or service name typos3 vs S3 (case matters in the credential scope). Double-check the service name in the AWS documentation for that specific endpoint.
  • Signature version mismatch — Make sure you're using aws4_request in the credential scope, not a previous version like aws2_request.

If none of that works, take a step back. Use an AWS SDK for your language — Python's boto3, JavaScript's AWS SDK, or the Go SDK v2. They handle signing perfectly. Then compare the signed request your SDK sends to the one your custom code generates. The SDK logs will show you the exact canonical request. Copy that into your code. That's the fastest path to a fix.

Was this solution helpful?