Skip to main content
A betterstack drain posts to one Better Stack source’s ingesting host. Records arrive with dt as the timestamp, level as the severity, message as the log line, and the whole Lua record kept alongside as structured fields you can query and alert on. Before you begin
  • A Better Stack source of type HTTP. Its Connect page gives you two things you need: the ingesting host and the source token.

What to enter

Output
The ingesting host is specific to your source and to the region the source lives in — it is not a single shared endpoint, so copy it from the Connect page rather than guessing.
Better Stack deliveries are not signed. There is no X-Lua-Signature header and no signing secret; the Authorization header authenticates the sender. If you need a signature, use a generic HTTPS drain.

How ownership verification works

Better Stack uses a test post: one batch containing a single lua.drain.test record is sent to the ingesting host, and any 2xx is accepted.
A 401 means the token is wrong or is missing its Bearer prefix — the stored value is sent as the whole header value, so Bearer is part of what you enter (and, with --header-from-env, part of the variable’s value). The drain stays in pending_verification and keeps buffering (for up to six hours) while you fix it. Verification is limited to 5 attempts per drain per hour.

What arrives

Each record becomes one entry in a JSON array:

Filtering a source by organization, agent, or environment

Better Stack indexes top-level keys as searchable fields and does not index inside a nested object. The batch’s resource — which is where the organization and agent ids live — rides inside lua, so on a source carrying several agents there was nothing to filter on. Once this is enabled on your deployment, each entry also carries four of those resource attributes as top-level keys:
They are a copy, not a move. The lua record is untouched and the batch’s resource stays exactly where the published schema says it is, so nothing you already query changes meaning. What is lifted is ids and names — and, once app fields are enabled for your deployment, the app.<key> fields your own code supplied, under those same keys:
Never a secret, never message content, and — ids, names and your own fields alike — never a value the scrubber has not already seen. Every string is scrubbed, and a drain’s redact list applied, before the encoder runs; lifting a key copies the value the record already carries and cannot reveal anything the record did not. A key whose resource value is missing or empty is omitted rather than sent blank, because an empty string is a value a Better Stack query matches and a drain answering lua.org.id:"" would be worse than one answering nothing.
Until this is enabled on your deployment, an entry carries the record and nothing else — so group by lua.attributes["lua.primitive.name"] for a primitive and by lua.eventName for a kind of event, and if you need the agent or the environment on every record in the meantime, send to an OpenTelemetry collector and promote the resource attributes there. The published collector config already copies service.name and the environment onto each record, and its exporter can forward to Better Stack.

Useful queries

And the fleet filter, once the top-level keys are enabled — one agent’s errors on a source that receives the whole organization:

Caps

Test the token with curl

Prove the host and the token before creating the drain. This sends exactly the shape a delivery has:
A 202 means accepted; the entry shows up in Live tail within a few seconds. A 401 means the token is wrong or the Bearer prefix is missing; a 404 usually means the ingesting host belongs to a different source or region. Then do the same through the real delivery path:

Alert on it

Better Stack alerts on a saved query. Three that are worth having from day one: The heartbeat is what makes the other two trustworthy: it arrives every five minutes while the drain is healthy, so its absence tells you the pipeline is down rather than that your agents went quiet. Delivery guarantees explains what it does and does not prove.

If it isn’t working

The stored header value is sent verbatim as Authorization. It must read Bearer <source token>, prefix included. Update it with lua drains update <id> --header Authorization and re-enter the whole value.
The ingesting host is per-source and per-region. Copy it from that source’s Connect page. A 3xx is treated as a failure and never followed, so a host that redirects has to be replaced with its final URL.
healthy means the ingesting host accepted the batch. Check that you are looking at the right source, and that the source’s retention and any archiving rule have not moved the data. lua drains deliveries <id> shows the status code and response excerpt for each recent batch.If it shows no recent batches, nothing is being selected rather than nothing being accepted — start with --min-severity, which defaults to info and drops every debug record. If a drain isn’t delivering has that check.

Next steps

Event schema

What every field under lua means.

Delivery guarantees

Retry, drops, heartbeat, and health states.

Ship logs to your stack

Selectors, states, and quotas.

lua drains

Every verb and flag.