How to create a webhook using Node, Express and Ngrok
Here is a quick example of how to subscribe to a webhook with a dummy server using a Mac
Step 1
Install ngrok using Homebrew, as documented here.
For Homebrew v2.6.x and below:
brew cask install ngrok
For Homebrew v2.7.x and above:
brew install --cask ngrok
Sign up for a free ngrok account.
Install your authtoken. To find YOUR_TOKEN, go to your ngrok dashboard.
In terminal, run:
ngrok authtoken YOUR_TOKEN
If you receive a “No such file or directory” error, try navigating to the /usr/local/bin directory and rerunning
the command as follows:
cd /usr/local/bin
ngrok authtoken YOUR_TOKEN
Please refer to ngrok’s documentation for further clarification.
Step 2
Install Node.js. We are using homebrew to install Node.js, but you can also download it here.
Open a terminal and run:
brew install node
Step 3
Create a new Node.js project in terminal:
mkdir strava-webhooks
cd strava-webhooks
touch index.js
npm init
npm install express body-parser --save
Step 4
This is is the code to run a dummy server that will help you complete a webhook subscription. It will not process webhook events for you. Please copy/paste the below into index.js:
'use strict';
const express = require('express');
const crypto = require('crypto');
const app = express().use(express.json({
verify: (req, res, buf) => { req.rawBody = buf.toString('utf-8'); }
}));
// Your webhook signing secret
const SIGNING_SECRET = "YOUR_SIGNING_SECRET";
// Your verify token. Should be a random string.
const VERIFY_TOKEN = "STRAVA";
app.listen(process.env.PORT || 80, () => console.log('webhook is listening'));
// Verifies the X-Strava-Signature header on incoming webhook events
function verifySignature(req) {
const header = req.headers['x-strava-signature'];
if (!header) return false;
const parts = Object.fromEntries(header.split(',').map(p => p.split('=', 2)));
const timestamp = parts['t'];
const signature = parts['v1'];
if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) return false;
const expected = crypto
.createHmac('sha256', SIGNING_SECRET)
.update(`${timestamp}.${req.rawBody}`)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
// Creates the endpoint for our webhook
app.post('/webhook', (req, res) => {
if (!verifySignature(req)) {
console.log("invalid signature, rejecting event");
return res.sendStatus(403);
}
console.log("webhook event received!", req.query, req.body);
res.status(200).send('EVENT_RECEIVED');
});
// Adds support for GET requests to our webhook
app.get('/webhook', (req, res) => {
let mode = req.query['hub.mode'];
let token = req.query['hub.verify_token'];
let challenge = req.query['hub.challenge'];
if (mode && token) {
if (mode === 'subscribe' && token === VERIFY_TOKEN) {
console.log('WEBHOOK_VERIFIED');
res.json({"hub.challenge":challenge});
} else {
res.sendStatus(403);
}
}
});
Step 5
Start the node server by running the following in terminal:
node index.js
Step 6
Open a new tab and start ngrok so your local server is reachable by internet:
ngrok http 80
Step 7
Log in or create a Strava account. After your account has been created, click here to link the Node server you created in Steps 3 - 6 to your Strava account.
After you create a subscription in step 8, webhook events will be pushed to this app when when an athlete revokes access to an application, or when an activity is created, deleted, or one of the following activity fields are updated, as documented here.
An important field in the App creation form is Authorization Callback Domain; here you’ll want to use the
forwarding url listed as the output from Step 6 (e.g. http://xyzabc123.ngrok.io) so that Strava
knows where to push webhook events.
Step 8
Finally, make a Postman or cURL request to subscribe to a webhook.
Please change the parameters as the following example includes dummy data:
- You can find your client secret and client id on your App info page.
- Use the same callback_url that you used in Step 7, appended with /webhook (e.g. http://xyzabc123.ngrok.io/webhook).
- Use the verify_tokenfrom Step 4.
curl -X POST \
https://www.strava.com/api/v3/push_subscriptions \
-F client_id=12345 \
-F client_secret=1234567abcdefg \
-F callback_url=https://1234abcd.ngrok.io/webhook \
-F verify_token=STRAVA
You should see WEBHOOK_VERIFIED printed to the terminal console if the request was successful.
Step 9
To test that your webhook is working, trigger any of the events listed in Step 7 that will result in the generation of a webhook push event.
If your webhook subscription worked, you’ll see EVENT_RECEIVED printed to the terminal console.