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 "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": 14097
26 },
27 {
28 "name": "visually_complete",
29 "value": 21317
30 },
31 {
32 "name": "lighthouse-seo-score",
33 "value": 75
34 },
35 {
36 "name": "lighthouse-best-practices-score",
37 "value": 75
38 },
39 {
40 "name": "lighthouse-accessibility-score",
41 "value": 87
42 },
43 {
44 "name": "lighthouse-performance-score",
45 "value": 10
46 },
47 {
48 "name": "lighthouse-pwa-score",
49 "value": 45
50 },
51 {
52 "name": "js-parse-compile",
53 "value": 6888
54 },
55 {
56 "name": "time-to-first-byte",
57 "value": 128
58 },
59 {
60 "name": "first-contentful-paint",
61 "value": 4433
62 },
63 {
64 "name": "first-meaningful-paint",
65 "value": 11649
66 },
67 {
68 "name": "firstRender",
69 "value": 3724
70 },
71 {
72 "name": "dom-size",
73 "value": 2110
74 },
75 {
76 "name": "estimated-input-latency",
77 "value": 859
78 },
79 {
80 "name": "consistently-interactive",
81 "value": 22607
82 },
83 {
84 "name": "first-interactive",
85 "value": 21248
86 },
87 {
88 "name": "json_body_size_in_bytes",
89 "value": 2901
90 },
91 {
92 "name": "json_size_in_bytes",
93 "value": 1747
94 },
95 {
96 "name": "image_body_size_in_bytes",
97 "value": 863765
98 },
99 {
100 "name": "image_size_in_bytes",
101 "value": 863790
102 },
103 {
104 "name": "font_body_size_in_bytes",
105 "value": 402256
106 },
107 {
108 "name": "font_size_in_bytes",
109 "value": 404248
110 },
111 {
112 "name": "js_body_size_in_bytes",
113 "value": 1815834
114 },
115 {
116 "name": "js_size_in_bytes",
117 "value": 425500
118 },
119 {
120 "name": "css_body_size_in_bytes",
121 "value": 1329225
122 },
123 {
124 "name": "css_size_in_bytes",
125 "value": 93630
126 },
127 {
128 "name": "html_body_size_in_bytes",
129 "value": 425563
130 },
131 {
132 "name": "html_size_in_bytes",
133 "value": 42739
134 },
135 {
136 "name": "page_wait_timing",
137 "value": 1740
138 },
139 {
140 "name": "page_size_in_bytes",
141 "value": 2530039
142 },
143 {
144 "name": "page_body_size_in_bytes",
145 "value": 5537450
146 },
147 {
148 "name": "asset_count",
149 "value": 59
150 },
151 {
152 "name": "onload",
153 "value": 22592
154 },
155 {
156 "name": "oncontentload",
157 "value": 12595
158 }
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": 8517
198 },
199 {
200 "name": "visually_complete",
201 "value": 11477
202 },
203 {
204 "name": "lighthouse-seo-score",
205 "value": 68
206 },
207 {
208 "name": "lighthouse-best-practices-score",
209 "value": 68
210 },
211 {
212 "name": "lighthouse-accessibility-score",
213 "value": 93
214 },
215 {
216 "name": "lighthouse-performance-score",
217 "value": 26
218 },
219 {
220 "name": "lighthouse-pwa-score",
221 "value": 36
222 },
223 {
224 "name": "js-parse-compile",
225 "value": 1501
226 },
227 {
228 "name": "time-to-first-byte",
229 "value": 108
230 },
231 {
232 "name": "first-contentful-paint",
233 "value": 3470
234 },
235 {
236 "name": "first-meaningful-paint",
237 "value": 8449
238 },
239 {
240 "name": "firstRender",
241 "value": 2398
242 },
243 {
244 "name": "dom-size",
245 "value": 727
246 },
247 {
248 "name": "estimated-input-latency",
249 "value": 17
250 },
251 {
252 "name": "consistently-interactive",
253 "value": 24364
254 },
255 {
256 "name": "first-interactive",
257 "value": 11378
258 },
259 {
260 "name": "json_body_size_in_bytes",
261 "value": 1716
262 },
263 {
264 "name": "json_size_in_bytes",
265 "value": 1011
266 },
267 {
268 "name": "image_body_size_in_bytes",
269 "value": 1968564
270 },
271 {
272 "name": "image_size_in_bytes",
273 "value": 1973146
274 },
275 {
276 "name": "font_body_size_in_bytes",
277 "value": 346672
278 },
279 {
280 "name": "font_size_in_bytes",
281 "value": 348381
282 },
283 {
284 "name": "js_body_size_in_bytes",
285 "value": 1077849
286 },
287 {
288 "name": "js_size_in_bytes",
289 "value": 271680
290 },
291 {
292 "name": "css_body_size_in_bytes",
293 "value": 720939
294 },
295 {
296 "name": "css_size_in_bytes",
297 "value": 62227
298 },
299 {
300 "name": "html_body_size_in_bytes",
301 "value": 51148
302 },
303 {
304 "name": "html_size_in_bytes",
305 "value": 9958
306 },
307 {
308 "name": "page_wait_timing",
309 "value": 198
310 },
311 {
312 "name": "page_size_in_bytes",
313 "value": 2666403
314 },
315 {
316 "name": "page_body_size_in_bytes",
317 "value": 4166888
318 },
319 {
320 "name": "asset_count",
321 "value": 73
322 },
323 {
324 "name": "onload",
325 "value": 28932
326 },
327 {
328 "name": "oncontentload",
329 "value": 7034
330 }
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}

Security & Verification

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.

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