Verification
Webhooks are a powerful tool, but without proper verification they can pose security risks. Verifying incoming requests:
-
ensures that they're authentic and from a trusted source
-
reduces the risk of malicious attacks
-
prevents replay attacks
Implementing webhook verification increases the security and reliability of your application's real-time communication with other services.
Signature key
Showpad generates a unique secret key for each webhook subscription and it will only be visible once during the creation process. Make sure to store this secret on a secure location.
There are two ways to create your webhook subscriptions, via:
Showpad Signature Header
Showpad uses the HMAC-SHA256 algorithm to sign our webhooks. The request payload is concatenated with a timestamp and then we generate a signature by hashing the concatenated string with the secret key:
-
x-showpad-signature-timestamp
- A Unix epoch timestamp (in seconds) that corresponds to the moment that the request is fired. -
x-showpad-signature-v1
- A Base64 HMAC SHA-256 hash that is generated from the HTTP request body and timestamp header. The hash is generated for a secret.
Example headers
x-showpad-signature-v1: FLauBV44UFC2oeY8AHcvGiUaL1Bx+FU5o+6ZM8KBdo0=
x-showpad-signature-timestamp: 1668017345
Signature Verification
To verify the signature, the server can recreate it by hashing the concatenated string with the same secret key and comparing it to the signature in the request header. If the signatures match, the request is considered authentic and can be processed.
This process ensures that only requests from the webhook provider with a valid secret key and timestamp can be processed, providing a secure method for verifying webhook requests.
Step | Description |
---|---|
1. | Check the timestamp. Extract the HTTP header x-showpad-signature-timestamp from the request and verify that it is no older than 5 minutes.
|
2. | Prepare the signed_payload string.Concatenate the following values: the payload body + . + x-showpad-signature-timestamp (as a string)Example: { "hello": "world" }.1669302166 |
3. | Generate the HMAC-SHA256 hash. Compute the HMAC of your signed_payload with a SHA256 hash function and the secret key. Be sure that the output of the hash function encoded to Base64. |
4. | Compare hashes. Extract the x-showpad-signature-v1 from the request and split the string on the commas to get a list of signatures. The request is valid if at least one signature in the list exactly matches with your own generated HMAC SHA256 hash. |
The raw body of the request is used to perform signature verification. Any manipulation of the request's raw body will cause the verification to fail.
Examples
- JavaScript
- Python
- Java
const crypto = require('crypto');
const key = 'my-secret';
function verifySignature(rawBody, headers) {
const signature = headers['x-showpad-signature-v1'];
const timestamp = headers['x-showpad-signature-timestamp'];
const verifier = `${rawBody}.${timestamp}`;
const hmac = crypto.createHmac('sha256', key);
hmac.update(verifier);
const hash = hmac.digest('base64');
console.log(hash);
if (hash === signature) {
return true;
}
return false;
}
import hashlib
import hmac
from base64 import b64encode
KEY = "my-secret"
def verify_signature(raw_body: str, headers: Dict[str, str]) -> bool:
signature = headers.get('x-showpad-signature-v1')
timestamp = headers.get('x-showpad-signature-timestamp')
verifier = f"{raw_body}.{timestamp}"
hmac_hash = hmac.new(bytes(KEY, "utf-8"), bytes(verifier, "utf-8"), hashlib.sha256)
result = b64encode(hmac_hash.digest()).decode("utf-8")
if signature == result:
return True
return False
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
class Controller {
private static final String KEY = "my-secret";
private static final String ALG = "HmacSHA256";
public boolean verifySignature(String rawBody, Map<String, String> headers)
throws NoSuchAlgorithmException, InvalidKeyException {
String signature = headers.get("x-showpad-signature-v1");
String timestamp = headers.get("x-showpad-signature-timestamp");
String verifier = String.format("%s.%s", body, timestamp);
SecretKeySpec secretKeySpec = new SecretKeySpec(KEY.getBytes(), ALG);
Mac mac = Mac.getInstance(ALG);
mac.init(secretKeySpec);
byte[] hmac = mac.doFinal(verifier.getBytes());
String base64Hmac = Base64.getEncoder().encodeToString(hmac);
if (signature.equals(base64Hmac)) {
return true;
}
return false;
}
}