Integrating Sentry's Web Vitals into Slack
Introduction
Web Vitals is an initiative by Google aimed at providing various metrics to quantify website performance. Previously, the Google Chrome Developers channel published a video introducing Core Web Vitals in Taiwanese Hokkien—the examples were super fun, and I highly recommend checking it out ↓
Nowadays, many websites implement workflows to measure Web Vitals, using quantified metrics to find areas for improvement. Currently, quite a few tools help developers measure Web Vitals, such as:
- Lighthouse
- PageSpeed Insights
- CrUX
- Search Console
Sentry’s Web Vitals Analytics Feature
However, fewer people might know that Sentry previously introduced a Web Vitals analytics feature1. It tracks various Web Vitals metrics such as LCP, FP, and CLS, allowing developers to view related charts in the dashboard and even conveniently marking the averages for you—very thoughtful.

However, Sentry only seems to retain data for 30 days, and it lacks comparison features—such as comparing last week’s metrics against this week’s metrics. Moreover, charts can only be viewed inside Sentry, which introduces context-switching overhead for other teams. As developers, it’s only natural to want to automate this entire workflow.
Looking into it further, although Sentry provides API integrations (if you have an API token), there didn’t seem to be any official API documentation for Web Vitals. Out of curiosity, I opened the browser console and found that the charts were actually fetched via an API. After copying the API endpoint and parameters and calling it via cURL, it worked! It turns out it just wasn’t documented.

There are two main APIs: (Since they are undocumented, parameters or paths might change over time)
/events-measurements-histogram/?.../eventsv2
Designing the Automation Workflow
Now that we have the data, we can integrate it into Slack for scheduled tracking. This also makes it easy to share with other teams, while anyone needing deeper details can jump directly into Sentry’s dashboard. Here is how the workflow looks:

A cron job calls the API (or you could implement it via serverless functions), fetches the data from Sentry, uploads it, and generates a report.
One thing to watch out for is the presence of + in the query parameters. If passed directly to encodeURIComponent or handled by URLSearchParams, it becomes %2B, which triggers an error on Sentry’s side. You’ll need to handle the + separately or ensure it doesn’t get encoded.
For example, the API’s query parameter looks like this: field=percentile(measurements.fp%2C+0.75)&field=percentile(measurements.fcp%2C+0.75)&.... You can define the data to return using a function-call-like syntax; for instance, percentile(measurements.fp,+0.75) returns the 75th percentile of FP data (the unit should be milliseconds). Notice that while , is encoded as %2C, the + is not encoded. If you encounter errors when building query parameters, this is a good place to start debugging.
The returned data structure looks like this:
{
"path": "/my-page",
"data": [
{
"count": 10,
"bin": 2000
},
...
]
}
Next, simply generate a .json file and upload it to S3 (or any other storage service). This allows you to compare it against yesterday’s (or earlier) data, generate a report, and send it to Slack:

Additionally, if you want to generate chart images, you can use chart.js and chartjs-node-canvas in Node.js. This allows you to render charts without running a browser (canvas). The only caveat is that if the corresponding fonts are not installed on the server, the layout might break or the font rendering might look distorted.

Regarding the choice of cron job runner: while there are many mature solutions available today—even hardcoding a crontab on a server works—we used the cron job feature in Drone CI. The main reason is that Drone’s configuration syntax is very straightforward, we have dedicated Drone CI servers internally, and company policies restrict using arbitrary external services. Integrating with Drone CI was the simplest approach to keep setup easy.
However, for reasons unknown, every time a cron event triggered, other pipelines would also run. So I ended up creating a separate branch with an adjusted .yaml to trigger the API:
steps:
- name: upload and report
image: byrnedo/alpine-curl
commands:
- curl -X POST YOUR_SERVER_ENDPOINT
And that completes the entire automation workflow! Now reports are posted to Slack daily, making it much easier for developers to track changes and see feedback.
Footnotes
Related Posts
- Recreating My Room with Three.js Using React Three Fiber, I brought my real room into the browser—turning physical objects into an interactive table of contents, and using spatial memory to tell the story of my life and work over the past few years.
- Things to Keep in Mind When Using Images in Frontend Development Expanding on Jake Archibald's article, this post organizes how modern responsive images should be written: why width/height are still necessary, when to use CSS aspect-ratio, how to choose between AVIF and WebP, and using picture/source/srcset for art direction on mobile devices.
- CSS field-sizing — Auto-resize Form Elements with a Single Line of CSS Previously, auto-resizing a textarea required listening to scrollHeight in JavaScript. With CSS field-sizing: content, a single line replaces it all, supporting textarea, input, and select. This article covers the pain points of older approaches and how to use field-sizing.
- Make Your Link Underlines Look Better: text-underline-offset By default, underlines sit very close to the text. Some designers dislike this look, and personally, I don't think it looks great either.