Hi everyone,
I’m trying to upload images to Shopify using the stagedUploadsCreate mutation, but I keep running into a SignatureDoesNotMatch error when making the POST request to the staged upload URL.
I’ve followed the recommended process outlined in Shopify’s API docs, but the upload consistently fails. I’d appreciate any insights into what might be going wrong.
Steps I’m Following:
-
Request a staged upload URL using stagedUploadsCreate
Success
-
Upload the image to the staged URL using cURL
Fails with SignatureDoesNotMatch
-
Register the file with Shopify using fileCreate (Not reached due to error in Step 2)
GraphQL Request for Staged Upload URL
mutation stagedUploadsCreate($input: [StagedUploadInput!]!) {
stagedUploadsCreate(input: $input) {
stagedTargets {
url
resourceUrl
parameters {
name
value
}
}
userErrors {
field
message
}
}
}
Variables:
{
"input": [
{
"filename": "22208.png",
"mimeType": "image/png",
"resource": "IMAGE",
"fileSize": "24567"
}
]
}
Response from Shopify (stagedUploadsCreate)
{
"data": {
"stagedUploadsCreate": {
"stagedTargets": [
{
"url": "https://shopify-staged-uploads.storage.googleapis.com/tmp/53086781626/files/5777d842-d5d2-4c25-9fd0-7f3eb6a8880f/22208.png?X-Goog-Algorithm=GOOG4-RSA-SHA256...",
"resourceUrl": "https://shopify-staged-uploads.storage.googleapis.com/tmp/53086781626/files/5777d842-d5d2-4c25-9fd0-7f3eb6a8880f/22208.png",
"parameters": [
{
"name": "content_type",
"value": "image/png"
},
{
"name": "acl",
"value": "private"
}
]
}
],
"userErrors": []
}
}
}
My PHP Upload Code
$stagedTarget = $stagedUploadResponse['body']['data']['stagedUploadsCreate']['stagedTargets'][0];
$uploadUrl = $stagedTarget['url'];
$uploadParams = [];
foreach ($stagedTarget['parameters'] as $param) {
$uploadParams[$param['name']] = $param['value'];
}
// Attach file correctly
$uploadParams['file'] = new CURLFile(
realpath($image_path),
mime_content_type($image_path),
basename($image_path)
);
echo "Uploading to: " . $uploadUrl . "\n";
print_r($uploadParams);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $uploadUrl);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $uploadParams);
curl_setopt($ch, CURLOPT_HTTPHEADER, []); // Removed Content-Type to let cURL set it automatically
$response = curl_exec($ch);
$uploadError = curl_error($ch);
$http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo "HTTP Status: $http_status\n";
echo "Upload Response: " . json_encode($response, JSON_PRETTY_PRINT) . "\n";
if ($uploadError || $http_status !== 200) {
echo "❌ Error uploading image: $uploadError\n";
exit();
}
Error Response from Staged Upload
{
"Code": "SignatureDoesNotMatch",
"Message": "Access denied.",
"Details": "The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.",
"StringToSign": "GOOG4-RSA-SHA256\n20250313T184208Z\n20250313/auto/storage/goog4_request\n05487618751a65ebf3d17799588490225e8f83d7320f5cf64e39b7c1ff142580",
"CanonicalRequest": "POST\n/tmp/53086781626/files/5777d842-d5d2-4c25-9fd0-7f3eb6a8880f/22208.png\nX-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=merchant-assets@shopify-tiers.iam.gserviceaccount.com%2F20250313%2Fauto%2Fstorage%2Fgoog4_request..."
}
Things I’ve Tried
-
Ensured all parameters from stagedUploadsCreate are passed as received
-
Removed manual Content-Type header from cURL request
-
Used absolute file paths in CURLFile
-
Checked file permissions & existence
-
Tried sleeping 5 seconds before making the upload request
Questions
• Am I missing any required parameters when sending the file to Shopify’s staged upload URL?
• Do I need to sign the request differently?
• Is there a specific cURL setting required to ensure the signature matches?
Any guidance would be greatly appreciated! Thanks in advance.