Loopio uses signing secrets to secure the webhook connections and user information. We generate a unique string, the signing secret, for each webhook and share it with the user. The user can then verify requests from Loopio by verifying signatures using the secret.
Each webhook subscription has a unique signing secret, which can be copied from the Signing Secret column in the Webhooks section of the Admin > Integrations screen.
In the header of a webhook HTTP request, a signature, which is the request body hashed with the signing secret, is included. If the signature that you computed based on the request matches the signature in the request header, the request is sent from Loopio and is authentic.
For security reasons, we recommend regenerating your signing secret on a monthly basis. You can regenerate your signing secret by clicking on the gear button and choosing Regenerate Secret. Then, click the Regenerate Secret button to get a new signing secret and share it with your dev team.
How to Verify a Request with the Signing Secret
Note: A pseudocode example is included below
- Find the signing secret of your webhook subscription, which can be copied from the Signing Secret column in the Webhooks section of the Integration page in the Admin menu. In this example, the signing secret is
c175462a1521f65c164a111debffafed
. - Retrieve the
x-loopio-content-signature
andx-loopio-request-timestamp
in the header on the HTTP request and the request body.
signature = request.headers['x-loopio-content-signature']
>>>d1c0f2782de8bf8634981edbdb75296b49f5f9e26f8d686260357ffa7bd75c72
timestamp = request.headers['x-loopio-request-timestamp']
>>>1625845911
body = req.body.asString()
>>>{"event":"libraryReview.assigned","context":{"libraryEntryId":3957025,"reviewId":694227,"assignedTo":{"id":"62071","email":"example.assignedto@loopio.com"},"assignedBy":{"type":"user","id":"79754","email":"example.assignedby@loopio.com"},"dueDate":"2021-07-14T00:00:00-04:00"}}
- Check if the timestamp differs from current time by more than 5 minutes to protect your connection from replay attacks. If it is, ignore the request.
absolute_value(time.time() - timestamp) > 60 * 5 ? true : false
>>>false - Concatenate the timestamp and body of request with a colon in between to obtain the basestring, in the form of
x-loopio-request-timestamp:request-body
. Use HMAC SHA256 to hash this basestring with the signing secret as the hashing key.basestring = timestamp + ‘:’ + body
>>>1625845911:{"event":"libraryReview.assigned","context":{"libraryEntryId":3957025,"reviewId":694227,"assignedTo":{"id":"62071","email":"example.assignedto@loopio.com"},"assignedBy":{"type":"user","id":"79754","email":"example.assignedby@loopio.com"},"dueDate":"2021-07-14T00:00:00-04:00"}}
hashed = basestring.hashWithSHA256()
>>>d1c0f2782de8bf8634981edbdb75296b49f5f9e26f8d686260357ffa7bd75c72 - Compare the computed signature with the
x-loopio-content-signature
in the header. If they match, the request is sent from Loopio.hashed == signature >>>true
For more information on working with Webhooks see our guide: How Do I Create and Manage Webhooks?