MetrikStack
Sources

Vercel

Vercel analytics without a script. Point a Vercel Log Drain at MetrikStack and every page request served by Vercel is counted, bots and AI crawlers included.

A Vercel Log Drain forwards your deployment's logs to any HTTPS endpoint. Point one at POST /v1/logs/vercel and MetrikStack turns the request logs into pageviews, without any code in your app or script on the page.

Drains are a Vercel feature of the Pro and Enterprise plans, and Vercel bills them per GB sent. On Hobby, use the browser script.

Create a source of kind logs for the site in the dashboard to get an ingest token (in_…).

Set up the drain

  1. Vercel dashboard → your team → Observability → Drains → Add Drain, choose Logs and a Custom endpoint.
  2. Endpoint: https://api.metrikstack.com/v1/logs/vercel
  3. Format: NDJSON (a JSON array is accepted too).
  4. Sources: Static and Function (lambda/edge). Build, firewall and external logs can stay off; MetrikStack skips them anyway.
  5. Environments: Production (add Preview if you want preview traffic counted in the same site).
  6. Custom headers: Authorization: Bearer in_….
  7. Save. Vercel verifies the endpoint right away (see below).

If the drain UI you are using cannot set headers, put the token in the URL instead; every /v1/logs/* endpoint accepts it as a query parameter:

https://api.metrikstack.com/v1/logs/vercel?token=in_...

A token in a URL ends up in Vercel's project settings and in request logs, so use the header when you can, and rotate the token from the dashboard if it leaks.

Endpoint verification

Before it starts sending, Vercel probes the endpoint and requires the response to carry an x-vercel-verify header with the value shown in the Vercel UI. Set that value as vercel_verify in the source's config (dashboard or API).

The header is on every response of /v1/logs/vercel, including GET (which is what Vercel probes) and error responses, so a mistyped token doesn't block verification.

Signature checking (optional)

Vercel signs each delivery with x-vercel-signature, the hex SHA-1 HMAC of the raw request body keyed with the drain's secret. Configure the secret as vercel_secret in the source config, and every unsigned or mismatched delivery is rejected with 403. With no secret configured the signature is ignored and the bearer token is the only credential.

What becomes a pageview

An entry counts when all of the following hold:

  • source is static, lambda or edge, and the entry has a proxy object, meaning it describes a real request and not build output or a function's stdout;
  • proxy.method is GET or HEAD;
  • proxy.path is not an asset path (same extension and prefix rules as every other source; see Access logs);
  • the status is not a 3xx redirect. A 304 counts: the browser checked whether the copy of the page it kept from an earlier visit is still current.

Everything else is counted in the response's skipped figure, not rejected.

Fields map like this:

VercelMetrikStack
proxy.pathpath + query
proxy.hosthostname
proxy.methodmethod
proxy.statusCodestatus_code
proxy.userAgent[0]user agent (bot classification, device, browser)
proxy.refererreferrer
proxy.clientIpvisitor hash + GeoIP country, then dropped
proxy.vercelCache, proxy.cacheIdcache_status
timestamp (ms)event timestamp
requestIdrequest_id

proxy.region is the Vercel edge region (fra1), not the visitor's location, so it is not stored as a region; country comes from the client IP at ingest.

Drains and the request-id merge

The drain only ever sees requests that go through Vercel's proxy. Traffic served by another CDN in front of Vercel, or by a rewrite to an external origin, never reaches it.

Vercel's requestId is the platform's own id. It is not the X-MetrikStack-Request-Id that the Node SDK sets on the response, and a drain entry carries no application response headers, so the two ids cannot be matched. In practice:

  • Drain as the only source: every page request is a pageview. This is the simplest and most accurate setup.
  • Drain plus the tracker script or the Node SDK: there is no shared request id, so merging falls back to the 60-second dedupe window on (site, visitor, path). That works for most sites. The drain adds the cached and bot traffic the script never sees; the script adds screen size, language, time on page and in-app navigation in single-page apps.

Response

{ "accepted": 42, "skipped": 118, "rejected": [] }

skipped covers build logs, function output, assets, redirects and non-GET requests. Batches arrive in near real time, and Vercel retries failed deliveries, so a short outage on our side doesn't lose data.

On this page