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
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 singlelua.drain.test record is sent to the ingesting host, and any 2xx is accepted.
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’sresource — 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:
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
Caps
Test the token with curl
Prove the host and the token before creating the drain. This sends exactly the shape a delivery has: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
401 on every delivery
401 on every delivery
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.404 or a redirect
404 or a redirect
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.Nothing arrives although the drain is healthy
Nothing arrives although the drain is healthy
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.

