Signatures
To remove the need for authentication on the receiving side, the Star Community API uses signatures on all webhook calls to authenticate itself and provide for a checksum/tamper protection.
The signature is provided in the X-Signature header on every request and should be validated before processing the event body.
Verification steps
- Fetch the public key from the appropriate endpoint:
https://api.test.starcommunity.app/.well-known/webhooks.key(test) orhttps://api.starcommunity.app/.well-known/webhooks.key(production). - Cache the key for at least a few minutes, possibly for hours. Do not cache forever.
- Get the request body and
X-Signatureheaders - Decode the Base64 encoded signature header
- Verify the request body against the decoded signature using the public key with the
RSA-SHA256algorithm - Only process the webhook request if the signature is correct
Testing your implementation
You can fetch an example JSON body with a X-Signature header from https://api.test.starcommunity.app/.well-known/webhook-example-body (test) or https://api.starcommunity.app/.well-known/webhook-example-body (production) using a HTTP GET request. Normally, this body and header would be sent in HTTP POST to your server directly.
Example Code
PHP
<?php
// Fetch public key from URL
$publicKeyUrl = "https://api.test.starcommunity.app/.well-known/webhooks.key"; // Replace with the URL you would like to use
$publicKey = file_get_contents($publicKeyUrl);
if ($publicKey === false) {
die("Failed to fetch public key from URL");
}
// TODO: Implement caching of the key, at least for a few minutes
// Example JSON body and X-Signature header
$jsonBody = '{"key": "value"}'; // Replace with actual JSON body recieved in your framework
$xSignature = "BASE64_SIGNATURE_HERE"; // Replace with actual signature received from the X-Signature header
// Decode the signature
$decodedSignature = base64_decode($xSignature);
if ($decodedSignature === false) {
die("Invalid Base64 signature");
}
// Verify the signature
$isValid = openssl_verify($jsonBody, $decodedSignature, $publicKey, OPENSSL_ALGO_SHA256);
if ($isValid === 1) {
echo "Signature is valid\n";
} elseif ($isValid === 0) {
echo "Signature is invalid\n";
} else {
echo "Error during signature verification\n";
}
?>
NodeJS
import { createVerify } from "crypto";
// Example JSON body and X-Signature header
const jsonBody = '{"key": "value"}'; // Replace with the actual JSON body from your frameework
const xSignature = "BASE64_SIGNATURE_HERE"; // Replace with the actual signature from the X-Signature header
// URL to fetch the public key
const publicKeyUrl = "https://api.test.starcommunity.app/.well-known/webhooks.key"; // Replace with the actual URL you would like to use
// Fetch the public key
const response = await fetch(publicKeyUrl);
if (!response.ok) {
throw new Error(`Failed to fetch public key: ${response.statusText}`);
}
const publicKey = await response.text();
// TODO: Implement caching of the key, at least for a few minutes
// Create a verifier
const verifier = createVerify("RSA-SHA256");
verifier.update(jsonBody);
verifier.end();
// Decode the Base64 signature
const decodedSignature = Buffer.from(xSignature, "base64");
// Verify the signature
const isValid = verifier.verify(publicKey, decodedSignature);
if (isValid) {
console.log("Signature is valid");
} else {
console.log("Signature is invalid");
}
Python
import base64
import requests
from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256
# Fetch public key from URL
public_key_url = "https://api.test.starcommunity.app/.well-known/webhooks.key" # Replace with the URL you would like to use
response = requests.get(public_key_url)
response.raise_for_status()
public_key_pem = response.text
# TODO: Implement caching of the key, at least for a few minutes
# Load the public key
public_key = RSA.import_key(public_key_pem)
# Example JSON body and X-Signature header
json_body = '{"key": "value"}' # Replace with actual JSON body recieved in your framework
x_signature = "BASE64_SIGNATURE_HERE" # Replace with actual signature received from the X-Signature header
# Compute the SHA256 hash of the JSON body
hashed_body = SHA256.new(json_body.encode('utf-8'))
# Decode the Base64 signature
decoded_signature = base64.b64decode(x_signature)
# Verify the signature
try:
pkcs1_15.new(public_key).verify(hashed_body, decoded_signature)
print("Signature is valid")
except (ValueError, TypeError):
print("Signature verification failed")