# Webhook Signature Verification Signature Verification can be used to authenticate requests sent to your endpoint. At a high level, each request contains headers that you can use to validate that the request came from our system. The following headers can be used to authenticate the request: * x-fr-wh-authorization - the signature * x-fr-wh-pk - full HTTPS URI to the public key contents Our implementation uses [digital signatures](https://en.wikipedia.org/wiki/Digital_signature) with the RSA with SHA algorithm; specifically SHA256 which for our purposes reduces the size of the signature without compromising its security. The process for authenticating a request involves two parties - __We__ (flexEngage) and __You__ (Merchants with configured webhook endpoints) - and is explained in simple terms as follows: 1. __We__ send HTTP requests to your configured Webhook Endpoint. The following elements from the request can be used to authenticate __we__ sent __you__ the request. * __Request Body__ - this is a body of the HTTP reqeust. * __x-fr-wh-authorization__ (Request Header) - an HTTP Request Header that contains the digital signature. * __x-fr-wh-pk__ (Request Header) - an HTTP Request Header that contains location (HTTPS URI) of the Public Key. 1. __You__ retrieve the Public Key contents from the Public Key location provided as an HTTP Request Header (x-fr-wh-pk). You should also validate the TLS certificate used in the https connection and verify that it was issued to one these domains (__NOTE__: Do not try to reuse this key with subsequent requests since it is not guaranteed that the same pair was used): * assets.webhooks.flexengage.com - This domain hosts Public Keys from our Production system. * assets.webhooks.flexengage-test.com - This domain hosts Public Keys from our Test system. 1. __You__ hash the Request Body using the SHA256 algorithim. Please consider the following in this step: * When decoding the Request Body to a string, do so using the UTF-8 charset. * Make sure no deserialization happens at this level since that might change the hash. 1. __You__ decode the Base64 signature in the header of the request (x-fr-wh-authorization). 1. __You__ verify the decoded signature matches the payload you previously hashed using the provided public key. ### Sample Code You can find sample code to guide you on how to verify the request signature below. Additionally, we have a public [git repository](https://bitbucket.org/flexreceipts/webhook-signature-samples/src/master/) with full examples on how to do the signature verification, including tests and dependencies. ### Java sample code ```java import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemReader; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.StringReader; import java.net.URL; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; public class SignatureVerifier { public static boolean verify( String payload, String signature, String pubKey, String algorithm) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException { PublicKey publicKey = getPublicKeyFromString(pubKey); Signature publicSignature = Signature.getInstance(algorithm); publicSignature.initVerify(publicKey); publicSignature.update(payload.getBytes(StandardCharsets.UTF_8)); byte[] signatureBytes = Base64.getDecoder().decode(signature); return publicSignature.verify(signatureBytes); } private static PublicKey getPublicKeyFromString(String keyFileContents) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { StringReader reader = new StringReader(keyFileContents); PemReader pemReader = new PemReader(reader); PemObject pemObject = pemReader.readPemObject(); byte[] keyContentAsBytes = pemObject.getContent(); X509EncodedKeySpec spec = new X509EncodedKeySpec(keyContentAsBytes); KeyFactory kf = KeyFactory.getInstance("RSA"); return kf.generatePublic(spec); } } ``` ### Python sample code This requires installation of the PyCryptodome package. [PyCryptodome installation instructions](https://pycryptodome.readthedocs.io/en/latest/src/installation.html) ```python import base64 from Crypto.Hash import SHA256 from Crypto.PublicKey import RSA from Crypto.Signature import pkcs1_15 def verify(payload, signature, pub_key): msg = payload.encode() hashed_payload = SHA256.new(msg) public_key = RSA.import_key(pub_key) signer = pkcs1_15.new(public_key) try: signer.verify(hashed_payload, base64.b64decode(signature)) return True except (ValueError, TypeError): return False ```