Attaching a PDF / API issues : Missing headers: x-goog-expires

Hello.
We’ve been attempting to make a PDF upload to shopify thanks to the documentation and this thread

The final goal is to attach these PDF files to customer orders later down the line via metafields.
With it, we’ve succeeded to get the staged upload site, however, any attempt at this point yields errors that we cannot find responses to.

We are currently working strictly on Postman to understand the request flow we seek

Our current progress is:
POST https://{{Environment}}.myshopify.com/admin/api/2024-01/graphql.json (Request Id = 1f80e45d-deb0-4b6f-980a-b2fb58d4a5cf)
Yields 200 with body:

{
  "data": {
    "stagedUploadsCreate": {
      "stagedTargets": [
        {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/pdf"
            },
            {
              "name": "success_action_status",
              "value": "201"
            },
            {
              "name": "acl",
              "value": "private"
            },
            {
              "name": "key",
              "value": "tmp/(numbers)/files/(Guid)/F1234.pdf"
            },
            {
              "name": "x-goog-date",
              "value": "20240125T093520Z"
            },
            {
              "name": "x-goog-credential",
              "value": "merchant-assets@shopify-tiers.iam.gserviceaccount.com/20240125/auto/storage/goog4_request"
            },
            {
              "name": "x-goog-algorithm",
              "value": "GOOG4-RSA-SHA256"
            },
            {
              "name": "x-goog-signature",
              "value": "(long string of chars)"
            },
            {
              "name": "policy",
              "value": "eyJjb25kaXRpb25zIjpbeyJDb250ZW50LVR5cGUiOiJhcHBsaWNhdGlvblwvcGRmIn0seyJzdWNjZXNzX2FjdGlvbl9zdGF0dXMiOiIyMDEifSx7ImFjbCI6InByaXZhdGUifSxbImNvbnRlbnQtbGVuZ3RoLXJhbmdlIiwxLDIwOTcxNTIwXSx7ImJ1Y2tldCI6InNob3BpZnktc3RhZ2VkLXVwbG9hZHMifSx7ImtleSI6InRtcFwvNzY1Mzk0OTQ3MDBcL2ZpbGVzXC8yZDM0NzA5Ny00OWM0LTRjMDItYjU0Mi1kN2Q4NDA1OWM0MWZcL0YxMjM0LnBkZiJ9LHsieC1nb29nLWRhdGUiOiIyMDI0MDEyNVQwOTM1MjBaIn0seyJ4LWdvb2ctY3JlZGVudGlhbCI6Im1lcmNoYW50LWFzc2V0c0BzaG9waWZ5LXRpZXJzLmlhbS5nc2VydmljZWFjY291bnQuY29tXC8yMDI0MDEyNVwvYXV0b1wvc3RvcmFnZVwvZ29vZzRfcmVxdWVzdCJ9LHsieC1nb29nLWFsZ29yaXRobSI6IkdPT0c0LVJTQS1TSEEyNTYifV0sImV4cGlyYXRpb24iOiIyMDI0LTAxLTI2VDA5OjM1OjIwWiJ9"
            }
          ],
          "url": "https://shopify-staged-uploads.storage.googleapis.com/",
          "resourceUrl": "https://shopify-staged-uploads.storage.googleapis.com/tmp/(numbers)/files/(Guid)/F1234.pdf"
        }
      ]
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 11,
      "actualQueryCost": 11,
      "throttleStatus": {
        "maximumAvailable": 2000.0,
        "currentlyAvailable": 1989,
        "restoreRate": 100.0
      }
    }
  }
}

However, when it comes to sending the file itself
both
PUT https://shopify-staged-uploads.storage.googleapis.com
and
PUT https://shopify-staged-uploads.storage.googleapis.com/tmp/(numbers)/files/(Guid)/F1234.pdf

with parameters (copied from Postman’s bulk edit):

Content-Type:application/pdf
success_action_status:201
acl:private
key:tmp/(numbers)/files/(Guid)/F1234.pdf
x-goog-date:20240125T093520Z
x-goog-credential:merchant-assets@shopify-tiers.iam.gserviceaccount.com/20240125/auto/storage/goog4_request
x-goog-algorithm:GOOG4-RSA-SHA256
x-goog-signature: (long string of chars)
policy:eyJjb25kaXRpb25zIjpbeyJDb250ZW50LVR5cGUiOiJhcHBsaWNhdGlvblwvcGRmIn0seyJzdWNjZXNzX2FjdGlvbl9zdGF0dXMiOiIyMDEifSx7ImFjbCI6InByaXZhdGUifSxbImNvbnRlbnQtbGVuZ3RoLXJhbmdlIiwxLDIwOTcxNTIwXSx7ImJ1Y2tldCI6InNob3BpZnktc3RhZ2VkLXVwbG9hZHMifSx7ImtleSI6InRtcFwvNzY1Mzk0OTQ3MDBcL2ZpbGVzXC8yZDM0NzA5Ny00OWM0LTRjMDItYjU0Mi1kN2Q4NDA1OWM0MWZcL0YxMjM0LnBkZiJ9LHsieC1nb29nLWRhdGUiOiIyMDI0MDEyNVQwOTM1MjBaIn0seyJ4LWdvb2ctY3JlZGVudGlhbCI6Im1lcmNoYW50LWFzc2V0c0BzaG9waWZ5LXRpZXJzLmlhbS5nc2VydmljZWFjY291bnQuY29tXC8yMDI0MDEyNVwvYXV0b1wvc3RvcmFnZVwvZ29vZzRfcmVxdWVzdCJ9LHsieC1nb29nLWFsZ29yaXRobSI6IkdPT0c0LVJTQS1TSEEyNTYifV0sImV4cGlyYXRpb24iOiIyMDI0LTAxLTI2VDA5OjM1OjIwWiJ9

and a PDF file, in binary, as the body
yields 400:


So, we’ve attempted to add headers as it requests (x-goog-expires:4800 and x-goog-signedHeaders:host), but we are then met with


Any help would be welcome!

Hi Killian- the MissingSecurityHeader error suggests that the required x-goog-expires header was not included in your PUT request. This header is necessary for Google Cloud Storage to know how long the signed URL should be valid. You mentioned adding x-goog-expires:4800, which should theoretically solve this issue. Ensure this header is correctly formatted and included in your request. The SignatureDoesNotMatch error is more complex. It indicates that the signature you’re providing doesn’t match what Google Cloud Storage expects. This is often due to:

  1. Incorrect Signing Method: Make sure you’re using the correct method for signing your request. It needs to match with what you’ve specified in the x-goog-algorithm header.

  2. Incorrect Secret Key: Verify that the secret key you’re using is correct and has the necessary permissions.

  3. String To Sign Mismatch: The string you’re using to generate the signature might not match what Google Cloud expects. Double-check that you’re including all the necessary components in the correct order: HTTP method, URL path, query parameters, headers, and payload hash.

  4. Time Sync Issue: Ensure that your system’s clock is synchronized with NTP. A time mismatch can lead to signature mismatches.

Hope this helps,

Hi,
We are trying to upload a file to store. But Response of stagedUploadsCreate only keys 2 parameters as content_type and acl. NO google headers.
And the url given in response , wehn we try it , gives 403 Forbidden.

Below is our grpahql request:

mutation generateStagedUploads {
  stagedUploadsCreate(input: [
    {
      filename: "tryon.png",
      mimeType: "image/png",
      resource: IMAGE
    }
  ])
    {
    stagedTargets {
      url
      resourceUrl
      parameters {
        name
        value
      }
    }
    userErrors {
      field, message
    }
  }
}