I am experiencing a persistent issue with webhook signature verification between my WordPress site and Square’s webhook system. Despite following the provided documentation for signature verification, the signatures generated on my end consistently do not match the ones provided by Square. This mismatch is leading to a 403 Forbidden
response, which I believe is due to Square rejecting the request based on invalid signatures. I would greatly appreciate your help in diagnosing and resolving this issue.
Details:
- Platform: WordPress (WooCommerce)
- Environment: Production
- Webhook Endpoint: https://sparkpressstudios.com/wp-json/wc-square-sync/v1/webhook
- Error Received:
- Status code:
403 Forbidden
- Logs show mismatched webhook signatures.
- Status code:
Steps I’ve Taken:
- Webhook Payload: I log the payload received from Square before any modifications are made:
- Payload is read directly from
php://input
to ensure it is not altered. - The exact payload is used for signature generation and comparison.Example payload (as received from Square):
json
Copy code
{
"merchant_id": "MLNTRH0XQCY55",
"type": "order.created",
"event_id": "a1416bf9-f4ab-3460-b018-2294038f5a3e",
"created_at": "2024-09-21T04:33:20Z",
"data": {
"type": "order_created",
"id": "htIpUQ6VcuE65GJrvMi7oSTPBnPZY",
"object": {
"order_created": {
"created_at": "2024-09-21T04:33:19.734Z",
"location_id": "LHTBF9SBMYCK5",
"order_id": "htIpUQ6VcuE65GJrvMi7oSTPBnPZY",
"state": "OPEN",
"version": 1
}
}
}
}
- Signature Generation:
- I compute the expected signature using the raw payload with the HMAC-SHA256 algorithm and the secret key obtained from the Square Developer Dashboard.
- After computing the HMAC, it is base64-encoded before comparing it with the received signature.
- Code for Signature Calculation:
php
Copy code
private function verify_square_webhook_signature($payload, $signature) {
$webhook_secret = get_option('wc_square_sync_webhook_secret');
if (empty($webhook_secret)) {
error_log('Webhook verification failed: No webhook secret set.');
return false;
}
// Compute the HMAC using raw payload and the secret key
$computed_hmac = hash_hmac('sha256', $payload, $webhook_secret, true);
$expected_signature = base64_encode($computed_hmac);
// Log for troubleshooting
error_log('Expected Signature: ' . $expected_signature);
error_log('Received Signature: ' . $signature);
// Perform timing-safe comparison
return hash_equals($expected_signature, $signature);
}
- Mismatch Example: Here’s an example of the mismatch between the expected and received signatures, as logged:
- Expected Signature:
4ZTw531aV5htqajQBU3fzIYsk3Ad7mQ00inc7B7htUw=
- Received Signature:
pXUK+6S0wbyrkeDi/J7laPSS+o431eijIx13X2U4MWw=
- Ensured the Following:
- The webhook secret used for signature generation matches exactly what is set in the Square Developer Dashboard.
- The payload is logged and used as received without any modifications (no trimming, whitespace, or encoding changes).
- The HMAC is calculated using the
sha256
algorithm, and the result is base64-encoded.
Things I Have Checked:
- Webhook Secret: Verified that the webhook secret used in my code is accurate and matches what’s provided in the Square Developer Dashboard.
- Payload Logging: Ensured that the payload is logged before any modification.
- Server Time: There are no significant time discrepancies that could cause issues, but I am open to further investigation here if needed.
Questions:
- Could there be any differences in how Square calculates and sends the signature compared to how I am calculating it on my end?
- Are there any known issues with how payloads are delivered that could cause this type of mismatch?
- Is there anything specific about the
403 Forbidden
response that might provide additional insight into why this mismatch is happening?
I would appreciate any guidance or troubleshooting steps you can provide.