Webhooks


Getting started

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.

Adding a webhook manually

  • Navigate to the site in Calibre, then Settings → Integrations → Add Webhook
  • Paste in the destination URL (Data will be delivered as application/json via HTTP POST)
  • Check the "Snapshot" event checkbox
  • Click "Save notification"
  • Webhooks are delivered after a snapshot has been completed

Adding a webhook pragmatically

Webhooks and other integrations can be managed pragmatically using the Node.js API. See the Integrations page for more information.

Example JSON output

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 "name": "iPhone X",
16 "status": "completed",
17 "endpoint": "https://www.apple.com/iphone-x/",
18 "canonical": false,
19 "profile": "iPhone 6, 3G connection",
20 "metrics": [
21 {
22 "name": "speed_index",
23 "value": 14097
24 },
25 {
26 "name": "visually_complete",
27 "value": 21317
28 },
29 {
30 "name": "lighthouse-seo-score",
31 "value": 75
32 },
33 {
34 "name": "lighthouse-best-practices-score",
35 "value": 75
36 },
37 {
38 "name": "lighthouse-accessibility-score",
39 "value": 87
40 },
41 {
42 "name": "lighthouse-performance-score",
43 "value": 10
44 },
45 {
46 "name": "lighthouse-pwa-score",
47 "value": 45
48 },
49 {
50 "name": "js-parse-compile",
51 "value": 6888
52 },
53 {
54 "name": "time-to-first-byte",
55 "value": 128
56 },
57 {
58 "name": "first-contentful-paint",
59 "value": 4433
60 },
61 {
62 "name": "first-meaningful-paint",
63 "value": 11649
64 },
65 {
66 "name": "firstRender",
67 "value": 3724
68 },
69 {
70 "name": "dom-size",
71 "value": 2110
72 },
73 {
74 "name": "estimated-input-latency",
75 "value": 859
76 },
77 {
78 "name": "consistently-interactive",
79 "value": 22607
80 },
81 {
82 "name": "first-interactive",
83 "value": 21248
84 },
85 {
86 "name": "json_body_size_in_bytes",
87 "value": 2901
88 },
89 {
90 "name": "json_size_in_bytes",
91 "value": 1747
92 },
93 {
94 "name": "image_body_size_in_bytes",
95 "value": 863765
96 },
97 {
98 "name": "image_size_in_bytes",
99 "value": 863790
100 },
101 {
102 "name": "font_body_size_in_bytes",
103 "value": 402256
104 },
105 {
106 "name": "font_size_in_bytes",
107 "value": 404248
108 },
109 {
110 "name": "js_body_size_in_bytes",
111 "value": 1815834
112 },
113 {
114 "name": "js_size_in_bytes",
115 "value": 425500
116 },
117 {
118 "name": "css_body_size_in_bytes",
119 "value": 1329225
120 },
121 {
122 "name": "css_size_in_bytes",
123 "value": 93630
124 },
125 {
126 "name": "html_body_size_in_bytes",
127 "value": 425563
128 },
129 {
130 "name": "html_size_in_bytes",
131 "value": 42739
132 },
133 {
134 "name": "page_wait_timing",
135 "value": 1740
136 },
137 {
138 "name": "page_size_in_bytes",
139 "value": 2530039
140 },
141 {
142 "name": "page_body_size_in_bytes",
143 "value": 5537450
144 },
145 {
146 "name": "asset_count",
147 "value": 59
148 },
149 {
150 "name": "onload",
151 "value": 22592
152 },
153 {
154 "name": "oncontentload",
155 "value": 12595
156 }
157 ],
158 "budget_alerts": null,
159 "artifacts": {
160 "filmstrip": {
161 "thumbs": [
162 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1682063.892.jpg",
163 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1682532.696.jpg",
164 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1685799.232.jpg",
165 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1686499.204.jpg",
166 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1686899.188.jpg",
167 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1687299.172.jpg",
168 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1688782.446.jpg",
169 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1690182.39.jpg",
170 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1691349.01.jpg",
171 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1692448.966.jpg",
172 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1693715.582.jpg",
173 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1703281.866.jpg",
174 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1703365.196.jpg",
175 "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/1703381.862.jpg"
176 ],
177 "video": "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/screencast.mp4",
178 "gif": "https://calibre-screenshots-prod.s3.amazonaws.com/05efb55f-0d2b-4c25-afd4-06dd7038fada/video-timeline/screencast.gif"
179 },
180 "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"
181 }
182 },
183 {
184 "id": "iphone-6",
185 "name": "iPhone 6",
186 "status": "completed",
187 "endpoint": "https://www.apple.com/iphone-6/",
188 "canonical": false,
189 "profile": "iPhone 6, 3G connection",
190 "metrics": [
191 {
192 "name": "speed_index",
193 "value": 8517
194 },
195 {
196 "name": "visually_complete",
197 "value": 11477
198 },
199 {
200 "name": "lighthouse-seo-score",
201 "value": 68
202 },
203 {
204 "name": "lighthouse-best-practices-score",
205 "value": 68
206 },
207 {
208 "name": "lighthouse-accessibility-score",
209 "value": 93
210 },
211 {
212 "name": "lighthouse-performance-score",
213 "value": 26
214 },
215 {
216 "name": "lighthouse-pwa-score",
217 "value": 36
218 },
219 {
220 "name": "js-parse-compile",
221 "value": 1501
222 },
223 {
224 "name": "time-to-first-byte",
225 "value": 108
226 },
227 {
228 "name": "first-contentful-paint",
229 "value": 3470
230 },
231 {
232 "name": "first-meaningful-paint",
233 "value": 8449
234 },
235 {
236 "name": "firstRender",
237 "value": 2398
238 },
239 {
240 "name": "dom-size",
241 "value": 727
242 },
243 {
244 "name": "estimated-input-latency",
245 "value": 17
246 },
247 {
248 "name": "consistently-interactive",
249 "value": 24364
250 },
251 {
252 "name": "first-interactive",
253 "value": 11378
254 },
255 {
256 "name": "json_body_size_in_bytes",
257 "value": 1716
258 },
259 {
260 "name": "json_size_in_bytes",
261 "value": 1011
262 },
263 {
264 "name": "image_body_size_in_bytes",
265 "value": 1968564
266 },
267 {
268 "name": "image_size_in_bytes",
269 "value": 1973146
270 },
271 {
272 "name": "font_body_size_in_bytes",
273 "value": 346672
274 },
275 {
276 "name": "font_size_in_bytes",
277 "value": 348381
278 },
279 {
280 "name": "js_body_size_in_bytes",
281 "value": 1077849
282 },
283 {
284 "name": "js_size_in_bytes",
285 "value": 271680
286 },
287 {
288 "name": "css_body_size_in_bytes",
289 "value": 720939
290 },
291 {
292 "name": "css_size_in_bytes",
293 "value": 62227
294 },
295 {
296 "name": "html_body_size_in_bytes",
297 "value": 51148
298 },
299 {
300 "name": "html_size_in_bytes",
301 "value": 9958
302 },
303 {
304 "name": "page_wait_timing",
305 "value": 198
306 },
307 {
308 "name": "page_size_in_bytes",
309 "value": 2666403
310 },
311 {
312 "name": "page_body_size_in_bytes",
313 "value": 4166888
314 },
315 {
316 "name": "asset_count",
317 "value": 73
318 },
319 {
320 "name": "onload",
321 "value": 28932
322 },
323 {
324 "name": "oncontentload",
325 "value": 7034
326 }
327 ],
328 "budget_alerts": null,
329 "artifacts": {
330 "filmstrip": {
331 "thumbs": [
332 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1683338.5.jpg",
333 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1683999.304.jpg",
334 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1685749.234.jpg",
335 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1686815.858.jpg",
336 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1686882.522.jpg",
337 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1688649.118.jpg",
338 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1689749.074.jpg",
339 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1691798.992.jpg",
340 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1693432.26.jpg",
341 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1693732.248.jpg",
342 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1694015.57.jpg",
343 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1694032.236.jpg",
344 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1694098.9.jpg",
345 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1694315.558.jpg",
346 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1694498.884.jpg",
347 "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/1694815.538.jpg"
348 ],
349 "video": "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/screencast.mp4",
350 "gif": "https://calibre-screenshots-prod.s3.amazonaws.com/67ec59cf-0121-4ab7-ab38-e2f4459c40da/video-timeline/screencast.gif"
351 },
352 "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"
353 }
354 }
355 ]
356}

Security & Verification

Webhooks can be securely validated using the Calibre-HMAC-SHA56-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.

Setting a shared secret

  1. Navigate to the webhook in question. (Site → Settings → Integrations)
  2. Use the automatically generated secret, or create your own.
  3. Press Save Notification.

Verifing the secret

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-time
2// Requires environment variable containing the secret: process.env.SECRET_TOKEN
3
4const crypto = require('crypto')
5const bufferEq = require('buffer-equal-constant-time')
6
7const verifyCalibreHMAC = (req, res, next) => {
8 const payload = req.body()
9 const payloadSignature = req.header('Calibre-HMAC-SHA256-Signature')
10
11 let hmac = crypto.createHmac('sha256', process.env.SECRET_TOKEN)
12 hmac.write(payload)
13 hmac.end()
14
15 const signature = hmac.read()
16
17 if (bufferEq(new Buffer(signature), new Buffer(payloadSignature))) {
18 next()
19 } else {
20 throw new Error('HMAC Signatures do not match')
21 }
22}
23
24// Express will downcase headers
25app.use(verifyCalibreHMAC)
26app.post('/calibre-webhook', (req, res) => {
27 res.send('We have a valid hmac signature')
28})

Notes:

  • In the examples above we have set the original secret as an environment variable. Never commit the secret to source control.
  • Comparing hashes using the equal comparison operator (==) is not advised as it cannot protect against timing attacks.

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.

Related Articles