epok

Send Vercel logs to Epok

Updated Jul 28, 2026 · 1d ago

Vercel Drains (previously called Log Drains; renamed in 2026 when the feature generalized to cover Logs, Traces, Speed Insights, and Analytics) forward observability data to any HTTPS endpoint. Point a Logs drain at Epok and runtime + build + edge logs flow in real time. No code changes, no sidecars, no agents.

Time to first log: ~5 min · Vercel plan: Pro or Enterprise (Hobby plan does not include Drains) · API key: app.getepok.dev → Settings → API Keys

1. Open Team Settings → Drains

From the Vercel dashboard, open your team and go to SettingsDrains → click Add Drain. (Drains live at the Team level, not per-project — one drain can scope across all projects or just the ones you pick.)

2. Choose data type: Logs

You'll see four data types — Logs, Traces, Speed Insights, Web Analytics. Pick Logs.

3. Name + projects + log sources

Give the drain a name (e.g. epok-logs), select which projects feed it (all or specific), and pick which log sources to forward. Vercel splits logs into:

  • Lambda — server function invocations (the highest-signal category)
  • Edge — edge function invocations (high volume; consider sampling)
  • Static — static asset cache events
  • Build — deployment build output
  • External — logs from external integrations

Starting point: enable Lambda + Build and leave Edge / Static off. Add Edge later with a sampling rule (next step) if you need that detail.

4. Sampling rules (optional)

By default the drain forwards 100% of selected sources. For high-volume Vercel deployments — especially Edge — you can add sampling rules per environment + path prefix. Common patterns:

  • Launch monitoring: 100% sample of production for a feature launch week, drop to 10% after.
  • Static coverage: 5% sample of /docs/* requests so you can catch regressions without paying for full coverage on a low-signal path.

Rules evaluate top-down. Requests not matching any rule are dropped — so if you add a rule, add a catch-all after the specific ones (empty path prefix, 100%) to keep the default-forward behavior.

5. Configure destination → Custom Endpoint

In the destination sidebar pick Custom Endpoint and fill in:

Endpoint URL
https://ingest.getepok.dev/api/v1/drain/vercel
Format
JSON
Signature Verification Secret
Leave the auto-generated value, or set your own. Epok echoes x-vercel-verifyautomatically, so the drain activates immediately. Epok doesn't verify x-vercel-signature today but you can rotate the secret later without re-creating the drain.
Custom Headers
Authorization: Bearer epk_REPLACE_ME

Replace epk_REPLACE_ME with your real key from Settings → API Keys.

6. Click Create Drain

Vercel sends a one-time test POST to verify the endpoint before activating. If the test returns non-200, the drain won't activate — double-check the Authorization header. On success the drain immediately starts forwarding. You can re-test any time with the Testbutton on the drain's row in the Drains list.

7. Which Vercel fields survive ingest

Vercel sends each log entry as a structured object, and Epok keeps the part of it that it can use. Worth knowing which is which before you build a search or an alert around a field name:

  • Renamed: message _msg, timestamp (epoch milliseconds) → _time, sourceservice.
  • Derived: level is kept when Vercel sends one (warning is normalized to warn), and computed from the HTTP status when it doesn't.
  • Carried as-is: requestId, statusCode, path, host, environment, projectId, deploymentId — plus proxy_method, proxy_statusCode, proxy_path, proxy_region and proxy_clientIp flattened out of a proxy block.
  • Dropped: projectName and traceId. The parser does not carry them today, so neither is searchable — use projectId to tell projects apart.

By default service is the Vercel log source lambda, edge, build — which is the right grouping for a single-project drain. If one drain feeds several projects and you would rather the Services page split by project, point service_fields at projectId under Settings → Detection → Field Mappings. Point it at projectName and you get an empty Services page, since that field never arrives.

Verify

  1. Hit any deployed Vercel route once.
  2. Open app.getepok.dev Live Tail. Vercel batches drain deliveries every few seconds, so within ~30 s you'll see entries whose service is the Vercel log source (lambda, edge, build), with level and requestId populated. Do not go looking for projectName — see step 7.
  3. Deploy something that emits a console.error / runtime exception. New Errors surfaces the fingerprinted pattern within a few minutes.

A drain that verified is not a drain that is authenticated

Read this one before you trust a green drain. Epok answers Vercel's verification probe before it checks your API key, deliberately: the probe arrives with no log body, and it has to be echoed back so the drain can activate at all. So a POST with an empty body returns 200 regardless of the key you set — which means a drain with a typo'd Authorization header can verify cleanly, activate, show green in the Vercel dashboard, and then deliver nothing. Verification proves the URL is reachable. It does not prove your key is good.

The second half of the problem is that Vercel does not show you our answer. The dashboard reports a delivery failure — a count, a flagged drain, an email once failures pile up — not the status code or the response body. You cannot tell 401 from 403 from 429 from the Vercel side. So take Vercel out of the loop and ask the endpoint yourself, with a body:

terminal
bash
# Send a Vercel-shaped entry. A body is REQUIRED — see the note above:
# an empty POST is answered by the verification handshake, before auth runs,
# and will return 200 no matter how wrong your key is.
curl -i -X POST https://ingest.getepok.dev/api/v1/drain/vercel \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer epk_REPLACE_ME' \
  -d '[{"message":"auth probe","timestamp":1750000000000,"source":"lambda","level":"info"}]'

# 200 {"status":"ok","accepted":1}            key + header are both fine
# 401 {"error":"unauthorized","detail":...}   no key found in ANY accepted header
# 403 {"error":"forbidden","detail":...}      a key WAS found, and rejected
# 429 {"error":"rate_limited",...}            key is FINE — quota or rate limit

401 and 403 are different failures. 401 means no credential was found in any accepted header — on a Vercel drain that is almost always the Custom Header being saved with the wrong name or a missing Bearer prefix. 403 means a key was read and then rejected: rotated, invalid, missing the ingest scope, or the tenant disabled. The detailfield says which. 429 is the one that is not an auth failure at all — an expired trial or a blown daily volume cap — and it is what a busy drain is most likely to hit, so read the code before you go re-issuing keys. Epok's ingest plane accepts X-API-Key, Authorization: Bearer, and Authorization: Basic with the key as the username — but not Authorization: Token, which does work on the app plane and so gets copied over from other docs. The whole matrix is in Authentication and the plan gates are in Limits & error codes.

If your drain configuration genuinely cannot carry a custom header, the drain endpoints also accept the key as a ?api_key= query parameter. A header always wins over it, so leaving a stale header in place will quietly override the query parameter you just fixed.

Common gotchas

  • Hobby plan.Vercel restricts Drains to Pro and Enterprise. If you're on Hobby, instrument your functions to POST to Epok directly via the Node / Python integration code instead — or upgrade to Pro.
  • Drains pricing. Pro/Enterprise drain forwarding is billed per delivered volume unit ($0.50/unit as of mid-2026). Sampling rules are the simplest cost lever — drop Edge / Static to 1–5% if you're hitting unexpected drain bills.
  • Edge function logs are noisy. Vercel Edge runs close to the user, so one page view can produce many log entries (cache lookups, region info, etc). Either exclude Edge from the drain source list or sample it at 1–5% from the sampling-rules step.
  • Auto-pause on persistent failure.Vercel emails you and flags the drain as errored when >80% of deliveries fail in the last hour OR more than 50 failures accumulate. Watch your inbox for vercel.com sender during the first day.

Your first query

Getting the drain green is not the finish line — reading the data back is. Every entry that arrives through this endpoint is stamped ingest_source:vercel, which makes it the one filter guaranteed to isolate exactly this pipeline and nothing else. Your tenant id is in Settings → API Keys, and in the app URL.

terminal
bash
curl -X POST https://app.getepok.dev/api/v1/tenants/YOUR_TENANT_ID/search \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
  "query": "ingest_source:vercel",
  "start": "-1h",
  "end": "now",
  "limit": 100
}'

A good result is a logs array with a count and an elapsed_ms beside it. Each entry has _msg (Vercel's message), a _timeparsed from Vercel's epoch-millisecond timestamp, a level, and a service set to the Vercel log source — lambda, edge, build, static or external. Request logs also carry requestId, statusCode, path, host, environment, projectId and deploymentId, plus flattened proxy_method / proxy_statusCode / proxy_region fields where Vercel sent a proxy block. The body accepts exactly five fields — query, start, end, limit, and a before timestamp cursor for paging backward.

Then the query you will actually keep. Vercel often omits level on request logs, so Epok derives it from the HTTP status on the way in — 5xx becomes error, 4xx becomes warn, everything else info. That means this finds your failing routes even though nothing in your code ever logged the word “error”:

request body
json
{
  "query": "ingest_source:vercel AND level:error",
  "start": "-1h",
  "end": "now",
  "limit": 100
}

The same thing in the product is Explore (same syntax, field list down the side) or Live Tail for the streaming view. A count of 0 with a green drain and a 200 from the probe above means the drain is filtering, not failing — check the sampling rules and the source list from steps 3 and 4. The full grammar is in Search syntax.

Logs flowing? Next step: wire up notification channels so alerts reach Slack, PagerDuty, or email.

Next

  • Traces (APM) — the next signal, and the one Vercel logs cannot give you. Spans turn statusCode:500 into the call that caused it, and light up per-route latency and error rate.
  • Metrics & infrastructure — for the databases and queues behind your functions, which are usually where a Vercel 500 actually starts.
  • Browser RUM & session replay — the front half of a Next.js app, which never touches a log drain.
  • Search syntax — filters, pipes, and the two time-range behaviours that surprise people.
  • Authentication — the full credential matrix behind the 401 / 403 triage above.