Skip to main content

Start it

That is the whole thing. It refuses to start without at least one key, because a collector that accepts anything is worse than one that is down.

Point your sites at it

The key is whatever you generated. The endpoint may be a bare origin: the ingest path is appended for you.

Docker

The compose file runs SQLite on a named volume by default and ships a postgres profile:

Configuration

Scoping a key to a site

Append @ and a host list to restrict what a key may report for. Wildcards cover any depth of subdomain, and the apex.
A key with no @ is unrestricted. Scoping narrows the blast radius if a key leaks: a stolen key can then only pollute the site it was already reporting for.

What happens to IP addresses

Verification runs against the raw address, always. It has to: the address is the one part of a request a crawler cannot dress up, and it is what the CIDR and reverse-DNS checks compare. What gets stored is your choice: Deferred reverse-DNS holds the raw address in memory only for as long as the lookup takes.
Set BEACON_IP_SALT. Without it a random salt is generated at boot and the same crawler hashes differently after every restart, which makes “how many distinct addresses” meaningless across a deploy.

What it stores

Two tables. beacon_hits is one row per classified hit; beacon_daily_stats is a pre-aggregated rollup per (date, agent), so a 500-hit batch costs one upsert per pair rather than 500. Both are created on start unless you disable migration. The verification columns are the interesting ones: unverified and spoofed_suspected are not the same thing and should not be charted together. The first means there was nothing to check against. The second means something checkable contradicts the claim.

Deferred verification

Most hits are settled inline by a CIDR match, which does no I/O. Providers that publish no ranges but do publish an authenticating reverse-DNS suffix are settled after the response, because a DNS round-trip per hit is far too slow for a 500-hit batch. That backfill updates both the hit and its daily rollup. Nothing is lost if it fails; the hit simply stays unverified.

Embedding it instead

If you already have an app, you do not need the HTTP server. The pipeline is exported on its own:
ingestBatch is the same function hosted SnowSEO runs in production. Implement the HitStore interface to persist wherever you like, or extend SqliteHitStore, PostgresHitStore or MemoryHitStore.