Webhooks can be added on a per Site basis in order to get Snapshot metrics, URLs to screenshots, videos and other media out of Calibre.
To create a new webhook, navigate to Settings → Integrations → Add a Webhook for a given Site. Paste in the payload URL (data will be delivered as application/json via HTTP POST), check the Snapshot notifications checkbox and click Save. Webhooks will be delivered after a Snapshot is completed.
Webhooks and other integrations can be managed pragmatically using the Node.js API. See the Integrations page for more information.
A webhook will be JSON (application/json) delivered via HTTP POST to an endpoint of your choosing. Here is an example of the output:
1{2 "id": 1485,3 "organisation_id": "your-organisation-id",4 "site_id": "apple",5 "primary_region_id": "us-east-1",6 "ref": null,7 "client": "web",8 "status": "completed",9 "html_url": "https://calibreapp.com/your-organisation-id/apple/snapshots/1485",10 "url": "https://calibreapp.com/api/sites/apple/snapshots/1485.json",11 "created_at": "2018-07-16T23:33:17.970Z",12 "pages": [13 {14 "id": "iphone-x",15 "uuid": "xxx-123-456-789-xxx",16 "name": "iPhone X",17 "status": "completed",18 "endpoint": "https://www.apple.com/iphone-x/",19 "canonical": false,20 "profile": "iPhone 6, 3G connection",21 "profile_uuid": "xxx-123-456-789-xxx",22 "metrics": [23 {24 "name": "speed_index",25 "value": 1409726 },27 {28 "name": "visually_complete",29 "value": 2131730 },31 {32 "name": "lighthouse-seo-score",33 "value": 7534 },35 {36 "name": "lighthouse-best-practices-score",37 "value": 7538 },39 {40 "name": "lighthouse-accessibility-score",41 "value": 8742 },43 {44 "name": "lighthouse-performance-score",45 "value": 1046 },47 {48 "name": "lighthouse-pwa-score",49 "value": 4550 },51 {52 "name": "js-parse-compile",53 "value": 688854 },55 {56 "name": "time-to-first-byte",57 "value": 12858 },59 {60 "name": "first-contentful-paint",61 "value": 443362 },63 {64 "name": "first-meaningful-paint",65 "value": 1164966 },67 {68 "name": "firstRender",69 "value": 372470 },71 {72 "name": "dom-size",73 "value": 211074 },75 {76 "name": "estimated-input-latency",77 "value": 85978 },79 {80 "name": "consistently-interactive",81 "value": 2260782 },83 {84 "name": "first-interactive",85 "value": 2124886 },87 {88 "name": "json_body_size_in_bytes",89 "value": 290190 },91 {92 "name": "json_size_in_bytes",93 "value": 174794 },95 {96 "name": "image_body_size_in_bytes",97 "value": 86376598 },99 {100 "name": "image_size_in_bytes",101 "value": 863790102 },103 {104 "name": "font_body_size_in_bytes",105 "value": 402256106 },107 {108 "name": "font_size_in_bytes",109 "value": 404248110 },111 {112 "name": "js_body_size_in_bytes",113 "value": 1815834114 },115 {116 "name": "js_size_in_bytes",117 "value": 425500118 },119 {120 "name": "css_body_size_in_bytes",121 "value": 1329225122 },123 {124 "name": "css_size_in_bytes",125 "value": 93630126 },127 {128 "name": "html_body_size_in_bytes",129 "value": 425563130 },131 {132 "name": "html_size_in_bytes",133 "value": 42739134 },135 {136 "name": "page_wait_timing",137 "value": 1740138 },139 {140 "name": "page_size_in_bytes",141 "value": 2530039142 },143 {144 "name": "page_body_size_in_bytes",145 "value": 5537450146 },147 {148 "name": "asset_count",149 "value": 59150 },151 {152 "name": "onload",153 "value": 22592154 },155 {156 "name": "oncontentload",157 "value": 12595158 }159 ],160 "budget_alerts": null,161 "artifacts": {162 "filmstrip": {163 "thumbs": [164 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1682063.892.jpg",165 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1682532.696.jpg",166 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1685799.232.jpg",167 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1686499.204.jpg",168 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1686899.188.jpg",169 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1687299.172.jpg",170 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1688782.446.jpg",171 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1690182.39.jpg",172 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1691349.01.jpg",173 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1692448.966.jpg",174 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1693715.582.jpg",175 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1703281.866.jpg",176 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1703365.196.jpg",177 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1703381.862.jpg"178 ],179 "video": "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/screencast.mp4",180 "gif": "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/screencast.gif"181 },182 "har": "https://calibre-screenshots-prod.s3.amazonaws.com/5e7ebd8f932d99e2a017fd1e16551221/https___www_apple_com_iphone_x_/har/20180716233507265.json?X-Amz-Expires=3600&X-Amz-Date=20180716T233521Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAITFRYATLYAH7W3VQ/20180716/us-east-1/s3/aws4_request&X-Amz-SignedHeaders=host&X-Amz-Signature=be3e0818f2b8c24114d2813faf83b11e3a6d6b8084c84fb44ecde27104215045"183 }184 },185 {186 "id": "iphone-6",187 "uuid": "xxx-123-456-789-xxx",188 "name": "iPhone 6",189 "status": "completed",190 "endpoint": "https://www.apple.com/iphone-6/",191 "canonical": false,192 "profile": "iPhone 6, 3G connection",193 "profile_uuid": "xxx-123-456-789-xxx",194 "metrics": [195 {196 "name": "speed_index",197 "value": 8517198 },199 {200 "name": "visually_complete",201 "value": 11477202 },203 {204 "name": "lighthouse-seo-score",205 "value": 68206 },207 {208 "name": "lighthouse-best-practices-score",209 "value": 68210 },211 {212 "name": "lighthouse-accessibility-score",213 "value": 93214 },215 {216 "name": "lighthouse-performance-score",217 "value": 26218 },219 {220 "name": "lighthouse-pwa-score",221 "value": 36222 },223 {224 "name": "js-parse-compile",225 "value": 1501226 },227 {228 "name": "time-to-first-byte",229 "value": 108230 },231 {232 "name": "first-contentful-paint",233 "value": 3470234 },235 {236 "name": "first-meaningful-paint",237 "value": 8449238 },239 {240 "name": "firstRender",241 "value": 2398242 },243 {244 "name": "dom-size",245 "value": 727246 },247 {248 "name": "estimated-input-latency",249 "value": 17250 },251 {252 "name": "consistently-interactive",253 "value": 24364254 },255 {256 "name": "first-interactive",257 "value": 11378258 },259 {260 "name": "json_body_size_in_bytes",261 "value": 1716262 },263 {264 "name": "json_size_in_bytes",265 "value": 1011266 },267 {268 "name": "image_body_size_in_bytes",269 "value": 1968564270 },271 {272 "name": "image_size_in_bytes",273 "value": 1973146274 },275 {276 "name": "font_body_size_in_bytes",277 "value": 346672278 },279 {280 "name": "font_size_in_bytes",281 "value": 348381282 },283 {284 "name": "js_body_size_in_bytes",285 "value": 1077849286 },287 {288 "name": "js_size_in_bytes",289 "value": 271680290 },291 {292 "name": "css_body_size_in_bytes",293 "value": 720939294 },295 {296 "name": "css_size_in_bytes",297 "value": 62227298 },299 {300 "name": "html_body_size_in_bytes",301 "value": 51148302 },303 {304 "name": "html_size_in_bytes",305 "value": 9958306 },307 {308 "name": "page_wait_timing",309 "value": 198310 },311 {312 "name": "page_size_in_bytes",313 "value": 2666403314 },315 {316 "name": "page_body_size_in_bytes",317 "value": 4166888318 },319 {320 "name": "asset_count",321 "value": 73322 },323 {324 "name": "onload",325 "value": 28932326 },327 {328 "name": "oncontentload",329 "value": 7034330 }331 ],332 "budget_alerts": null,333 "artifacts": {334 "filmstrip": {335 "thumbs": [336 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1683338.5.jpg",337 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1683999.304.jpg",338 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1685749.234.jpg",339 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1686815.858.jpg",340 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1686882.522.jpg",341 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1688649.118.jpg",342 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1689749.074.jpg",343 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1691798.992.jpg",344 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1693432.26.jpg",345 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1693732.248.jpg",346 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1694015.57.jpg",347 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1694032.236.jpg",348 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1694098.9.jpg",349 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1694315.558.jpg",350 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1694498.884.jpg",351 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1694815.538.jpg"352 ],353 "video": "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/screencast.mp4",354 "gif": "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/screencast.gif"355 },356 "har": "https://calibre-screenshots-prod.s3.amazonaws.com/f477b5ec59c5a357f1f7d756b23c4645/https___www_apple_com_iphone_6_/har/20180716233446500.json?X-Amz-Expires=3600&X-Amz-Date=20180716T233521Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAITFRYATLYAH7W3VQ/20180716/us-east-1/s3/aws4_request&X-Amz-SignedHeaders=host&X-Amz-Signature=86e1fe079e3ce90c81bf81a62369c71c03c31640412cd22dce92c1996f4247b7"357 }358 }359 ]360}
Webhooks can be securely validated using the Calibre-HMAC-SHA256-Signature header. This header should be used in order to establish that the incoming request originates from Calibre.
This header is formulated by supplying a shared secret with Calibre.
Calibre uses HMAC cryptographic hash signature for verification purposes.
In order to verify the Calibre request, you will need to use the shared secret that you supplied earlier.
1// Example built for express.js, using buffer-equal-constant-time2// Requires environment variable containing the secret: process.env.SECRET_TOKEN34const crypto = require('crypto')5const bufferEq = require('buffer-equal-constant-time')67const verifyCalibreHMAC = (req, res, next) => {8 const payload = req.body()9 const payloadSignature = req.header('Calibre-HMAC-SHA256-Signature')1011 let hmac = crypto.createHmac('sha256', process.env.SECRET_TOKEN)12 hmac.write(payload)13 hmac.end()1415 const signature = hmac.read()1617 if (bufferEq(new Buffer(signature), new Buffer(payloadSignature))) {18 next()19 } else {20 throw new Error('HMAC Signatures do not match')21 }22}2324// Express will downcase headers25app.use(verifyCalibreHMAC)26app.post('/calibre-webhook', (req, res) => {27 res.send('We have a valid hmac signature')28})
1// SECRET_TOKEN is an environment variable23post '/calibre-webhook' do4 request.body.rewind5 payload_body = request.body.read6 verify_signature(payload_body)78 # Safely use the JSON9end1011def verify_signature(payload_body)12 signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), ENV['SECRET_TOKEN'], payload_body)13 return halt 500, "Signatures didn't match!" unless ActiveSupport::SecurityUtils.secure_compare(signature, request.env['Calibre-HMAC-SHA256-Signature'])14end
Notes:
We recommend that you use a third party webhook test service, or enable verbose logging in order to verify the webhooks are delivered in the format as expected.