Webhooks


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

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.

Webhook creation form

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

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.

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})
1// SECRET_TOKEN is an environment variable
2
3post '/calibre-webhook' do
4 request.body.rewind
5 payload_body = request.body.read
6 verify_signature(payload_body)
7
8 # Safely use the JSON
9end
10
11def 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:

  • 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.