Validating payload from Facebook webhook in Ruby on Rails
You can directly jump to the method here. Link for Gist
I was working on FB APIs. I added a webhook to receive real-time updates to my subscribed objects. Facebook API makes this flow pretty simple. After I subscribe to an object with specific fields (i.e, I can choose to receive changes to certain fields of an object), FB sends a webhook to my application if these fields are changed.
You don’t have to validate the payload, but you should. — Facebook Docs
Now, FB doesn’t require one to validate a webhook that is you can parse and do anything with all webhooks without validating them. Sounds pretty easy but this can have many issues like someone can simply throw lots of requests at your application and it might crash handling all this data without validating whom it came from.
So, It is advised to validate the request. FB docs have pretty simple steps on how to do this here. I was working in Ruby on Rails and couldn’t found anything specific to RoR. So, I am here :). Remember we are doing this in RoR but a similar approach can be used in any language of your choice.
Assumptions
- You have set up your RoR application.
- You have webhooks in Fb Dev Application.
Validating Payloads
FB signs all Event Notification payloads with a SHA1 signature and includes the signature in the request’s X-Hub-Signature
header preceded with sha1=
. To validate the payload there are 2 simple steps:
- Generating a
SHA1
signature using the received payload and your app’s App Secret (You can find this in Fb Developer Application Homepage). - Comparing this generated signature to the signature in the
X-Hub-Signature
header (everything aftersha1=
). If the signatures matches, the payload is genuine and hence request is validated.
Let’s do this then. Remember we are doing this in Rails but a similar approach can be used in any other language.
Read Payload
payload = request.body.read
Get Signature Header
sig_header = request.headers["X-Hub-Signature"]
# Only considering part after sha1= in signature header
sig_header.slice! "sha1="
Get your APP SECRET
I am using Figaro for environment variables management, you can use any method of your choice
app_secret = Figaro.env.FB_APP_SECRET
Generate SHA Signature from Payload and APP Secret
sign = get_sha_sign(payload, app_secret)def get_sha_sign(payload, app_secret)
return OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), app_secret.encode("ASCII"), payload.encode("ASCII"))
end
Validate Signatures
If the generated signature matches with the signature from the header, then the request is validated and handle your request however you want else just discard the webhook.
if sign != sig_header
raise BaseError::InvalidRequest.new("Invalid Signature")
end
render plain: "OK"
Below is the complete implementation of the same discussed above.
Troubleshooting
- Handle route properly, correctly map the callback URL to the above handle_webhook method. Remember a POST request is received in the case of webhooks.
- Can’t verify CSRF token authenticity.
Internal Error: ActionController::InvalidAuthenticityToken
This is a common issue you might simple the method isn’t able to handle the request. What I advise you to do here is addingskip_before_action :verify_authenticity_token
just below the class declaration in the controller containing the webhook handler.
You can read more about this here - Invalid App Secret
A pretty common mistake is APP Secret is not set. Verify if your app secret is being retrieved via rails console. Figaro or env can be a simple option to work with env variables.
Any suggestions or changes are welcome 😃.