> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bronto.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Vercel Drains

> What each Vercel drain sends to Bronto, how to create and filter drains yourself, delivery formats and signatures, what happens when deliveries fail, and what drains cost.

A drain forwards what Vercel's platform records (logs, Vercel's spans, Web Vitals, page views) to an HTTP endpoint as it happens. The [Marketplace connector](/integrations/vercel-connector.md) creates drains for every project in one step. This page covers what drains send and how to create them yourself.

## When to create drains yourself

* You want drains for some projects only, or one drain for several projects.
* You want your own dataset and collection names.
* You want to route some log sources to their own dataset as well, for example `firewall` logs to a security team's dataset.
* You manage drains in your own scripts or infrastructure code.

## Drain types

| Data | Schema | Encoding | Bronto endpoint |
| - | - | - | - |
| Logs | `log` `v1` | NDJSON | `https://ingestion.eu.bronto.io` |
| Traces | `trace` `v1` | Protobuf (OTLP) | `https://ingestion.eu.bronto.io/v1/traces` |
| Speed Insights | `speed_insights` `v1` | NDJSON | `https://ingestion.eu.bronto.io` |
| Web Analytics | `analytics` `v2` | NDJSON | `https://ingestion.eu.bronto.io` |
| AI Gateway traces | `ai_gateway` `otelv1` | Protobuf (OTLP) | `https://ingestion.eu.bronto.io/v1/traces` |

Use `ingestion.us.bronto.io` for the US region. Every drain authenticates with the header `x-bronto-api-key` and an API key with the **Ingestion** role (see [API Keys](/Account-Management/API-Keys.md)). Log, Speed Insights and Web Analytics drains also set `x-bronto-dataset` and `x-bronto-collection`, which decide where the data lands. Trace drains don't need them: traces appear in [Explore Traces](/tracing/explore-traces.md), not in datasets.

What each drain contains:

* **Logs**: seven sources, in the field `source`: `lambda` (functions), `edge`, `static`, `build`, `external` (external rewrites), `firewall` and `redirect`. Each record carries `deploymentId`; request records add `environment`, `executionRegion` and the request's `proxy.*` fields, and function logs from traced requests carry `traceId` and `spanId`. See [Functions](/integrations/vercel-functions.md), [Builds](/integrations/vercel-builds.md) and [CDN, routing and firewall](/integrations/vercel-cdn-firewall.md).
* **Traces**: Vercel's spans for each request (`vercel.edge-network`, `vercel.serverless-runtime`). Spans from your own code reach Bronto directly through [OpenTelemetry from code](/integrations/vercel-otel.md).
* **Speed Insights**: Core Web Vitals from real users. Requires Speed Insights Plus. See [Speed Insights](/integrations/vercel-speed-insights.md).
* **Web Analytics**: page views and custom events. See [Web Analytics](/integrations/vercel-web-analytics.md).
* **AI Gateway traces**: one trace per model request. Vercel's REST API docs don't list this schema; create it in the dashboard. See [Vercel AI SDK, AI Gateway and agents](/integrations/vercel-ai.md).

## Create a drain yourself

### In the dashboard

1. Open **Team Settings → Drains → Add Drain**.
2. Choose the data (logs, traces, Speed Insights or Web Analytics) and the projects.
3. For logs, choose the sources and environments to send.
4. Set the endpoint and format from the [table above](#drain-types), and add the headers `x-bronto-api-key`, `x-bronto-dataset` and `x-bronto-collection`.

Saving the drain sends three `Test log message` records. Filter them out if you count events.

### With the REST API

A log drain for one project, sent to the dataset `vercel-logs` in the collection `my-app`:

```bash theme={"dark"}
curl -X POST \
  "https://api.vercel.com/v1/drains?teamId=<TEAM_ID>" \
  -H "Authorization: Bearer <VERCEL_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-app-logs",
    "projects": "some",
    "projectIds": ["<PROJECT_ID>"],
    "schemas": { "log": { "version": "v1" } },
    "delivery": {
      "type": "http",
      "endpoint": "https://ingestion.eu.bronto.io",
      "encoding": "ndjson",
      "headers": {
        "x-bronto-api-key": "<YOUR_API_KEY>",
        "x-bronto-dataset": "vercel-logs",
        "x-bronto-collection": "my-app"
      }
    }
  }'
```

A trace drain sends OTLP protobuf, and its endpoint is an object:

```json theme={"dark"}
{
  "name": "my-app-traces",
  "projects": "some",
  "projectIds": ["<PROJECT_ID>"],
  "schemas": { "trace": { "version": "v1" } },
  "delivery": {
    "type": "http",
    "endpoint": {
      "traces": "https://ingestion.eu.bronto.io/v1/traces"
    },
    "encoding": "proto",
    "headers": { "x-bronto-api-key": "<YOUR_API_KEY>" }
  }
}
```

For Web Analytics, use `"schemas": { "analytics": { "version": "v2" } }`; for Speed Insights, `"speed_insights": { "version": "v1" }`.

## Filter what a drain sends

A filter decides which records a drain forwards. Use it to route: a second drain with a filter sends some of the records to another dataset as well, while the main drain sends everything. Set it on create or with `PATCH /v1/drains/{id}`. `version` must be `"v2"`.

Only production build and firewall logs:

```json theme={"dark"}
{
  "filter": {
    "version": "v2",
    "filter": {
      "type": "basic",
      "log": { "sources": ["firewall", "build"] },
      "deployment": { "environments": ["production"] }
    }
  }
}
```

A filter can also be a text expression. This one keeps only firewall logs, from every environment:

```json theme={"dark"}
{
  "filter": {
    "version": "v2",
    "filter": {
      "type": "odata",
      "text": "source eq 'firewall'"
    }
  }
}
```

> [!NOTE]
>
> When you read a drain with `GET /v1/drains/{id}`, its filter is returned in the field `filterV2`. The field `filter` stays empty, so check `filterV2` to see which filter a drain applies.

A filter keeps or drops whole records. Everything a drain does forward reaches Bronto in full.

## Formats, batching and delivery time

* **`ndjson`** sends `Content-Type: application/x-ndjson`, one record per line. **`json`** sends `application/json`, one JSON array per request. The connector and the drains in this guide use NDJSON.
* **`compression: "gzip"` may not compress.** Vercel's docs say deliveries can be compressed, but on a test log drain over 20 minutes and 1,700 requests (up to 53 KB each) no request carried `Content-Encoding` and every body was plain JSON. The cost is the same either way (see [What drains cost](#what-drains-cost)).
* **Batches are small.** In a test project with about 225 log records a minute, Vercel sent about 56 requests a minute: a median of 3 records per request, at most 65. The median record was 1.2 KB.
* **Delivery is near real time.** Half of the records arrived within 5 seconds of their timestamp, 95% within about a minute, all within about 2 minutes.
* **Changes take minutes to apply.** After a `PATCH`, deliveries kept the old settings for 2 to 4 minutes.

## Verify deliveries with a signature

When a drain has a secret, every request carries the header `x-vercel-signature`: an HMAC-SHA1 of the request body, hex-encoded, keyed with the secret. Drains created through the REST API have no secret until you set one (`"delivery": { "secret": "…" }` on create or by `PATCH`).

Bronto's endpoint authenticates drains by API key. The signature matters when a drain goes to your own receiver first, for example a forwarder that enriches records:

```ts theme={"dark"}
import { createHmac, timingSafeEqual } from "node:crypto";

function validSignature(body: Buffer, signature: string) {
  const hmac = createHmac("sha1", process.env.DRAIN_SECRET!)
    .update(body)
    .digest("hex");
  const expected = Buffer.from(hmac);
  const received = Buffer.from(signature);
  return expected.length === received.length
    && timingSafeEqual(expected, received);
}
```

Compute it over the raw body as received, before parsing.

## When deliveries fail

**Vercel doesn't buffer a failing drain.** In a test, the receiving endpoint answered `500` for 15 minutes:

* Vercel sent each batch twice: the retry came about 0.2 seconds after the first attempt (at most 0.5 seconds), then the batch was dropped.
* Of 1,803 records created in those 15 minutes, 1,767 (98%) were never delivered. Nothing was resent after the endpoint recovered.
* Deliveries resumed within a second of recovery.

Vercel reports failing drains: when more than 80% of deliveries fail, or more than 50 in an hour, it sends a notification email and marks the drain as errored on the Drains page. See [Errored Drains](https://vercel.com/docs/drains/using-drains#errored-drains).

So a drain is only as reliable as the endpoint it sends to. If a drain goes to your own receiver first, keep it simple and highly available, and have it queue records rather than fail the request.

## What drains cost

Vercel charges \*\*$0.50 per GB** for drains on Pro, measured as the uncompressed JSON of each record, whatever the delivery encoding. AI Gateway trace drains add $0.05 per 1,000 traces.

| Volume | Records (at 1.2 KB) | Cost |
| - | - | - |
| 1 GB | about 850,000 log records | \$0.50 |
| A project with \~225 log records a minute | about 0.38 GB a day | about $0.19 a day, $6 a month |

To see what drains cost per project, query Vercel's billing API, `GET /v1/billing/charges`. It returns one line per service and day (`ServiceName: Drains Volume`) with the `ProjectId`.

Bronto keeps everything a drain sends, so send everything, once. The volume that's worth removing is duplicates: a trace drain also forwards your own spans, so if your code exports them directly as well, every span arrives twice (see [OpenTelemetry from code](/integrations/vercel-otel.md#with-a-trace-drain-on-the-same-project)). Use [filters](#filter-what-a-drain-sends) to route records, not to drop them.

## Known limits

* **Drains need a Pro or Enterprise plan.**
* **Changes take minutes to apply.** After a `PATCH`, deliveries keep the old settings for 2 to 4 minutes. A drain's filter is in `filterV2`, not in `filter`.
* **A plain trace drain doesn't carry AI Gateway traces.** They need their own `ai_gateway` drain.

## References

* [Vercel Drains](https://vercel.com/docs/drains)
* [Drains REST API](https://vercel.com/docs/rest-api/drains)
* [Log Drains reference](https://vercel.com/docs/drains/reference/logs)
* [Traces reference](https://vercel.com/docs/drains/reference/traces)
* [Securing drains](https://vercel.com/docs/drains/security)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.