I have to create a webhook from typeform to firebase. I will create a cloud function listening to events sent from typeform. The typeform is managed by a third party.
The only issue I have, is the authorization part for the webhook. I understood (from reading different post) that anyone can "talk" to the cloud function URL. But I would like to have a secure and exclusive communication between typeform and firebase.
Any hints ?
Thank for your time.
You can definitively connect a Typeform webhook to a Cloud function and push data to Firebase storage.
In addition to authentication pointed by Frank, Typeform also provides a signature mechanism to ensure that the request comes from Typeform webhook.
Typeform lets you define a secret to sign the webhook payload.
When you receive the payload on your end, in the cloud function, you verify first if it's signed correctly, if it's not it means it's not coming from Typeform, therefore, you should not deal with it.
Here is an example to verify the webhook signature:
app.post('/typeform/webhook', async (request, response) => {
console.log('~> webhook received');
// security check, let's make sure request comes from typeform
const signature = request.headers['typeform-signature']
const isValid = verifySignature(signature, request.body.toString())
if (!isValid) {
throw new Error('Webhook signature is not valid, someone is faking this!');
}
//valid signature let's do something with data received
})
And here is the verifySignature function
const crypto = require('crypto')
const verifySignature = function(receivedSignature, payload){
const hash = crypto
.createHmac('sha256', webhookSecret)
.update(payload)
.digest('base64')
return receivedSignature === `sha256=${hash}`
}
There are more details on Typeform documentation.
Hope it helps :)
Calling request.body.toString() does not work the way it is described in @Nicolas Greniés answer. The result will always be the string "[Object object]", as it only utilizes the default prototype as described here (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString).
A valid approach to stringify req.body would be to use JSON.stringify() which would still not deliver the expected result as you need to hash the original binary data (https://developer.typeform.com/webhooks/secure-your-webhooks/).
Use app.use(bodyParser.raw({ type: 'application/json' })) as specified here (Validate TypeForm Webhook payload in Node) to get the raw binary data and pass the request body directly into the hashing function.
const bodyParser = require("body-parser");
app.use(bodyParser.raw({ type: "application/json" })); // Notice .raw !
app.post("/typeform-handler", (req, res) => {
const hash = crypto
.createHmac('sha256', MY_TYPEFORM_SECRET)
.update(req.body) // Pass the raw body after getting it using bodyParser
.digest('base64')
})
If you are using a Firebase Cloud Function to handle the request, you can't use bodyParser this way as Firebase already takes care of the parsing (https://firebase.google.com/docs/functions/http-events#read_values_from_the_request). Instead, use req.rawBody to access the raw body and pass it to the hash function.
// No need for bodyParser
app.post("/typeform-handler", (req, res) => {
const hash = crypto
.createHmac('sha256', MY_TYPEFORM_SECRET)
.update(req.rawBody) // Notice .rawBody instead of just .body
.digest('base64')
})
Remark for TypeScript users
The default Express Request object does not contain a rawBody property. Be aware that TypeScript therefore might throw an error of no overload matches this call or Property 'rawBody' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'. The actual Request object will however be provided by Firebase and will contain said properties. You can access the actual Request object type using functions.https.Request.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With