Anyone on the internet can send a POST to /webhooks/payments, not only the provider. Right now a forged event saying payment.succeeded for order 9 marks it paid, and 1250 worth of goods would ship for free. The provider proves that an event comes from it by signing it with a secret that only it and your shop know.
How signing works: the provider computes an HMAC of the exact bytes of the body with the secret (here with SHA-256) and sends it, as lowercase hex, in the X-Signature header. Your shop computes the same HMAC and compares the two. Nobody can compute it without the secret, and changing one character of the body changes it completely. With another key and message:
var sig = Convert.ToHexString(HMACSHA256.HashData(Encoding.UTF8.GetBytes("key"), Encoding.UTF8.GetBytes("hello"))).ToLower();
Console.WriteLine(sig.Length); // 64 hexadecimal characters
Console.WriteLine(CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(sig), Encoding.UTF8.GetBytes("abc"))); // False
Compare with CryptographicOperations.FixedTimeEquals, not ==: == stops at the first different character, so an attacker who measures the time can find the signature one character at a time. When the signatures don't match, answer 400 before the event is read, so a forged event changes nothing:
POST /webhooks/payments (signed with the secret) → 200 {"received": true}
POST /webhooks/payments (a made-up X-Signature) → 400 {"error": "Bad signature"}
POST /webhooks/payments (no X-Signature header) → 400 {"error": "Bad signature"}
GET /orders/9 → 200 {…, "payment_id": null, "status": "pending", "total": 1250}
Your task: store the secret "whsec_test" in WEBHOOK_SECRET. In the webhook route, right below body, compute the expected signature from the body, read the X-Signature header, and answer {"error": "Bad signature"} with 400 when the two don't match.
Assignment
Follow the Coding Tutorial to check the signature of every event that reaches POST /webhooks/payments.
HintEncoding.UTF8.GetBytes turns text into bytes: the secret, the body and both signatures. Convert.ToHexString gives uppercase hex, so add .ToLower() to match the provider's. .ToString() on request.Headers["X-Signature"] gives "" when the header is missing, so a missing header simply doesn't match. HMACSHA256 and CryptographicOperations come from System.Security.Cryptography, already at the top of the file.
Free plan: 30 tutor messages. PRO is unlimited.
