Create webhooks
This topic describes how to set up webhooks in order to poll for changes to your datafile.
To get the most value when using Optimizely, you will want changes made to your feature flags and rollouts in the Optimizely UI to take effect in your applications as soon as possible.
One way to do this is to have your application poll for changes in the datafile that contains all the latest information on your feature flags and rollouts. However, if you are using Optimizely in an application with a server-side component, we recommend configuring a webhook.
Important
Currently, Optimizely supports webhooks only for the primary environment.
Note you might get some "noise" in your webhook notifications since a datafile environment in any environment causes all your datafiles in all environments to rebuild with a new version number. This means your primary environment might trigger a webhook notification, even though there were no significant changes to the primary environment other than a new version number.
By using a webhook, Optimizely can send a notification to your server when the datafile updates, eliminating the need to constantly poll and determine if there are changes in the Optimizely configuration. Once you receive the webhook and download the latest datafile, you must re-instantiate the Optimizely client with the latest datafile for the changes to take effect as soon as possible.
Follow the instructions below to setup a webhook and test that it is working correctly.
1. Set up an endpoint on your server
The first step to setting up a webhook is to set up a public API endpoint on your server, which can accept a POST request from Optimizely.
We recommend naming your endpoint /webhooks/optimizely
. However, you can name the endpoint anything you would like.
Setting up a public endpoint on your server varies greatly depending on the language and framework that you use. The examples below show Node (JavaScript) using Express and Python using Flask.
// Simple Node Express webhook example (see full example in step 3)
// Requires installing express: 'npm install --save express'
const express = require('express');
const app = express();
/**
* Optimizely Webhook Route
* Route to accept webhook notifications from Optimizely
**/
app.use('/webhooks/optimizely', (req, res, next) => {
console.log(`
[Optimizely] Optimizely webhook request received!
The Optimizely datafile has been updated. Re-download
the datafile and re-instantiate the Optimizely SDK
for the changes to take effect
`);
res.send('Webhook Received');
});
app.get('/', (req, res) => res.send('Optimizely Webhook Example'))
const HOST = process.env.HOST || '0.0.0.0';
const PORT = process.env.PORT || 8080;
app.listen(PORT, HOST);
console.log(`Example App Running on http://${HOST}:${PORT}`);
# Simple Python Flask webhook example (see full example in step 3)
# Requires installing flask: 'pip install flask'
import os
from flask import Flask, request, abort
app = Flask(__name__)
# Route to accept webhook notifications from Optimizely
@app.route('/webhooks/optimizely', methods=['POST'])
def index():
print("""
[Optimizely] Optimizely webhook request received!
The Optimizely datafile has been updated. Re-download
the datafile and re-instantiate the Optimizely SDK
for the changes to take effect
""")
return 'Webhook Received'
@app.route('/')
def hello_world():
return 'Optimizely Webhook Example'
if __name__ == "__main__":
host = os.getenv('HOST', '0.0.0.0')
port = int(os.getenv('PORT', 3000))
print('Example App unning on http://' + str(port) + ':' + str(host))
app.run(host=host, port=port)
2. Create a webhook in Optimizely
Once you have set up an endpoint on your server, take note of the fully qualified URL of the endpoint you created on your server. If your server domain is https://www.your-example-site.com
and you named your endpoint /webhooks/optimizely
as described in step 1, then your fully qualified webhook URL is https://www.your-example-site.com/webhooks/optimizely
. Use this URL in the steps below:
- Navigate to Settings > Webhooks.
- Click Create New Webhook...
- Enter the URL where Optimizely will send datafile update notifications (example:
https://www.your-example-site.com/webhooks/optimizely
) - Click Save.
- Take note of the secret generated to secure your webhook in the next step.
The example below shows the webhook payload structure with the default Optimizely primary environment
of "Production". Note: your environment
value may be different. Currently, we support one event type: project.datafile_updated
.
{
"project_id": 1234,
"timestamp": 1468447113,
"event": "project.datafile_updated",
"data": {
"revision": 1,
"origin_url": "https://optimizely.s3.amazonaws.com/json/1234.json",
"cdn_url": "https://cdn.optimizely.com/json/1234.json",
"environment": "Production"
}
}
3. Secure your webhook
Once you have set up a public endpoint that can accept datafile update notifications, you will want to secure your webhook to ensure that these notifications are actually coming from Optimizely and not from some malicious user trying to control your server's feature flag configuration.
When you create a webhook, Optimizely generates a secret token that is used to create a hash signature of webhook payloads. Webhook requests include this signature in a header X-Hub-Signature
that can be used to verify the request originated from Optimizely.
You can only view a webhook's secret token once, immediately after its creation. If you forget a webhook's secret token, you must regenerate it on the Settings > Webhook page.
The X-Hub-Signature
header contains a SHA1 HMAC hexdigest of the webhook payload, using the webhook's secret token as the key and prefixed with sha1=
. The way you verify this signature varies, depending on the language of your codebase. The reference implementation examples below show Node (JavaScript) using Express and Python using Flask.
Both examples assume your webhook secret is passed as an environment variable named OPTIMIZELY_WEBHOOK_SECRET
.
// Simple Node Express webhook example (see full example in step 3)
// Requires installing express: 'npm install --save express body-parser'
const express = require('express');
const bodyParser = require('body-parser')
const crypto = require('crypto');
const app = express();
/**
* Optimizely Webhook Route
* Route to accept webhook notifications from Optimizely
**/
app.use('/webhooks/optimizely', bodyParser.text({ type: '*/*' }), (req, res, next) => {
const WEBHOOK_SECRET = process.env.OPTIMIZELY_WEBHOOK_SECRET
const webhook_payload = req.body
const hmac = crypto.createHmac('sha1', WEBHOOK_SECRET)
const webhookDigest = hmac.update(webhook_payload).digest('hex')
const computedSignature = `sha1=${webhookDigest}`
const requestSignature = req.header('X-Hub-Signature')
if (!crypto.timingSafeEqual(Buffer.from(computedSignature, 'utf8'), Buffer.from(requestSignature, 'utf8'))) {
console.log(`[Optimizely] Signatures did not match! Do not trust webhook request")`)
res.status(500)
return
}
console.log(`
[Optimizely] Optimizely webhook request received!
Signatures match! Webhook verified as coming from Optimizely
Download Optimizely datafile and re-instantiate the SDK Client
For the latest changes to take affect
`);
res.sendStatus(200)
});
app.get('/', (req, res) => res.send('Optimizely Webhook Example'))
const HOST = process.env.HOST || '0.0.0.0';
const PORT = process.env.PORT || 8080;
app.listen(PORT, HOST);
console.log(`Example App Running on http://${HOST}:${PORT}`);
# Reference Flask implementation of secure webhooks
# Requires installing flask: 'pip install flask'
# Assumes webhook's secret is stored in the environment variable OPTIMIZELY_WEBHOOK_SECRET
from hashlib import sha1
import hmac
import os
from flask import Flask, request, abort
app = Flask(__name__)
# Route to accept webhook notifications from Optimizely
@app.route('/webhooks/optimizely', methods=['POST'])
def index():
request_signature = request.headers.get('X-Hub-Signature')
webhook_secret = bytes(os.environ['OPTIMIZELY_WEBHOOK_SECRET'], 'utf-8')
webhook_payload = request.data
digest = hmac.new(webhook_secret, msg=webhook_payload, digestmod=sha1).hexdigest()
computed_signature = 'sha1=' + str(digest)
if not hmac.compare_digest(computed_signature, request_signature):
print("[Optimizely] Signatures did not match! Do not trust webhook request")
abort(500)
return
print("""
[Optimizely] Optimizely webhook request received!
Signatures match! Webhook verified as coming from Optimizely
Download Optimizely datafile and re-instantiate the SDK Client
For the latest changes to take affect
""")
return 'Secure Webhook Received'
@app.route('/')
def hello_world():
return 'Optimizely Webhook Example'
if __name__ == "__main__":
host = os.getenv('HOST', '0.0.0.0')
port = int(os.getenv('PORT', 3000))
print('Example App unning on http://' + str(port) + ':' + str(host))
app.run(host=host, port=port)
Note
Be sure to keep all webhook secret tokens for your implementation secure and private.
Warning
To prevent timing analysis attacks, we strongly recommend that you use a constant time string comparison function such as Python's
hmac.compare_digest
or Rack'ssecure_compare
instead of the==
operator when verifying webhook signatures.
4. Test your implementation
To help ensure that your secure webhook implementation is correct, below are example values that you can use to test your implementation. These test values are also used in the running Node example in Runkit.
Example webhook secret token
yIRFMTpsBcAKKRjJPCIykNo6EkNxJn_nq01-_r3S8i4
Example webhook request payload
{
"timestamp": 1558138293,
"project_id": 11387641093,
"data":
{
"cdn_url": "https://cdn.optimizely.com/datafiles/QMVJcUKEJZFg8pQ2jhAybK.json",
"environment": "Production",
"origin_url": "https://optimizely.s3.amazonaws.com/datafiles/QMVJcUKEJZFg8pQ2jhAybK.json",
"revision": 13
},
"event": "project.datafile_updated"
}
Using the webhook secret token and the webhook payload as a string, your code should generate a compute signature of sha1=b2493723c6ea6973fbda41573222c8ecb1c82666
, which can be verified against the webhook request header:
Example webhook request header
X-Hub-Signature: sha1=b2493723c6ea6973fbda41573222c8ecb1c82666
Content-Type: application/json
User-Agent: AppEngine-Google; (+http://code.google.com/appengine; appid: s~optimizely-hrd)
Note
Use a tool like Request Bin to test your webhooks. Request Bin lets you create a webhook URL to process HTTP requests and render the data in a human-readable format. Click Create a RequestBin on the Request Bin website and use the resulting URL as your endpoint URL for testing.
Updated over 1 year ago