Start learning free

LESSON 110 OF 173 · BACKEND: PAYMENTS AND OTHER SERVICES

Check the Webhook's Signature in Java

Practice

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 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. In Java, a Mac computes it. With another key and message:

Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec("key".getBytes(), "HmacSHA256"));
String sig = HexFormat.of().formatHex(mac.doFinal("hello".getBytes()));
System.out.println(sig.length());   // 64 hexadecimal characters
System.out.println(MessageDigest.isEqual(sig.getBytes(), "abc".getBytes()));   // false

Compare with MessageDigest.isEqual, not equals: equals 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 the constant WEBHOOK_SECRET. At the top of paymentWebhook, compute the expected signature from the raw 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.


Hint
body already holds the raw text as sent, and .getBytes() turns it and the secret into bytes. request.getHeader gives null for a missing header: Objects.requireNonNullElse swaps it for "", which simply doesn't match.


STEP 1 OF 7
    Ctrl + Enter to check

    Pay Webhook Signature in Java

    Keep going with Java1,600+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free