带签名验证的Webhook接收器搭建:计算哈希偶尔差一个字符
I’ve seen this exact edge case pop up with Xero Webhooks before—especially when the Webhook Key contains slashes like your mrj/yJ7pZKejaRrN61vAJB example. The single-character hash mismatch usually boils down to one of these common issues, let’s walk through fixes:
1. Fix Webhook Key Corruption
The / in your key is a red flag here. If your app is storing or fetching the key incorrectly (e.g., auto-URL-encoding it, or treating it as a filesystem path that mangles slashes), you’ll compute an HMAC with the wrong key entirely—leading to that subtle hash difference.
- How to verify:
- Copy-paste your Webhook Key directly from the Xero Developer Portal into your code temporarily. If the verification works, your stored key is corrupted.
- Ensure your code isn’t running any string manipulation (like
urldecode()oraddslashes()) on the key before passing it tohash_hmac.
2. Ensure Payload Integrity
Even one byte difference in the payload will create a drastically different HMAC, which can show up as a single character change in the Base64 output.
- Common payload issues:
- Using
$_POSTinstead of reading the raw payload:$_POSTcan modify data (e.g., stripping newlines or encoding special characters). Always usephp://inputto get the exact payload Xero sends. - Character encoding mismatches: Xero sends payloads in UTF-8. If your PHP environment is using a different encoding (like ISO-8859-1) to process the payload, you’ll generate an HMAC from the wrong byte sequence.
- Using
- Fix code snippet:
// Read raw payload correctly $payload = file_get_contents('php://input'); // Confirm encoding is UTF-8 if (mb_detect_encoding($payload) !== 'UTF-8') { $payload = mb_convert_encoding($payload, 'UTF-8'); }
3. Check Base64 Consistency
Xero uses standard Base64 (with +, /, and = padding) for the X-Xero-Signature header. Your code’s base64_encode(hash_hmac(... true)) is correct, but watch out for:
- Accidental URL-safe Base64 conversion: Some libraries auto-replace
/with_and+with-—this will break the hash match. - Unintended string manipulation: Don’t
trim(), uppercase, or lowercase the computed hash before comparing it to Xero’s signature. - Quick check: Echo both your computed hash and Xero’s signature to see if the
/in one is replaced with another character (likeYin your case) — this will confirm if encoding is the issue.
4. Test with a Minimal Script
Isolate the problem with a tiny test script that uses your exact key and a sample Intent to Receive payload from Xero. This will rule out environment or middleware issues:
$webHookKey = 'mrj/yJ7pZKejaRrN61vAJB...'; // Your full key here $samplePayload = '{"events":[],"firstEventSequence":0,"lastEventSequence":0,"entropy":"your-test-entropy"}'; // Use Xero's exact test payload $computedHash = base64_encode(hash_hmac('sha256', $samplePayload, $webHookKey, true)); // Compare directly echo "Your Hash: " . $computedHash . "\n"; echo "Xero Hash: " . $_SERVER['HTTP_X_XERO_SIGNATURE'] . "\n";
If none of these work, check if there’s a reverse proxy or server middleware (like Cloudflare, Nginx) modifying request headers or payloads. Sometimes proxies alter special characters in headers without warning.
内容的提问来源于stack exchange,提问作者Healyhatman

