Skip to content

Instantly share code, notes, and snippets.

@brunojppb
Created October 11, 2019 11:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brunojppb/ad2a2ccdb4de121d87ee84a0e7e51ef9 to your computer and use it in GitHub Desktop.
Save brunojppb/ad2a2ccdb4de121d87ee84a0e7e51ef9 to your computer and use it in GitHub Desktop.
Handling HelpScout webhooks in Play Framework and Scala
/**
* How to handle HelpScout webhooks using Play Framework & Scala (Without using the HelpScout library)
* See original docs: https://developer.helpscout.com/webhooks/
*/
/** On your controller.
* Map this function to a valid route
* and register your webhook using the HelpScout API
*/
def postWebhook: Action[RawBuffer] = actionBuilder.async(parse.raw) {
implicit request =>
request.body.asBytes() match {
case Some(rawBody) =>
val decodedString = rawBody.decodeString(Charset.forName("UTF-8"))
val signature = request.headers.get("X-Helpscout-Signature").getOrElse(throw new IllegalArgumentException("Missing signature"))
val notificationEvent = request.headers.get("X-HelpScout-Event").getOrElse(throw new IllegalArgumentException("Missing event"))
HelpScoutAPI.parseNotification(body, signature) match {
case Some(json) => // Notification is legit
// Handle your payload here
Ok(Json.obj())
case None => // Probably a fake request
Ok(Json.obj())
}
case None =>
Ok(Json.obj())
}
}
/** on your HelpScout helper */
object HelpScoutAPI {
private val webhookSecretKey = "YOUR_SECRET_HERE"
def parseNotification(payload: String, helpscoutSignature: String): Option[JsValue] = {
val isValid = validateNotification(payload, helpscoutSignature)
if (isValid) {
Some(Json.parse(payload))
} else {
logger.info("Invalid notification")
None
}
}
/**
* Validate HelpScout payload using HMAC Signatures
* see: https://developer.helpscout.com/webhooks/#verifying
* */
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import org.apache.commons.codec.binary.Base64
private def validateNotification(payload: String, signature: String): Boolean = {
val mac = Mac.getInstance("HmacSHA1")
mac.init(new SecretKeySpec(webhookSecretKey.getBytes("UTF-8"), "HmacSHA1"))
val digest = payload.getBytes("UTF-8")
val signedPayload = mac.doFinal(digest)
val computedSignature = new String(Base64.encodeBase64(signedPayload)).trim
computedSignature == signature
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment