Obsero ingestion Documentation
The contract GitHub

The contract

The URL you POST to, and the payload. Everything else is yours to decide.

Which cloud, which log source, which language, whether you buffer or send per request, whether you use Terraform at all — none of that is part of the contract. This page is.

#The request

one event per HTTP request
POST https://analytics-staging.obsero.ai/v1/events

content-type:     application/json
x-obsero-domain:  acme.com          # which site this event belongs to
x-obsero-key:     <tracking ID>     # proves you own it

{
  "path": "/pricing",
  "method": "GET",
  "statusCode": 200,
  "headers": {
    "host": "acme.com",
    "user-agent": "Mozilla/5.0 ...; compatible; ChatGPT-User/1.0; +https://openai.com/bot",
    "accept": "*/*",
    "signature-agent": "\"https://chatgpt.com\"",
    "signature-input": "sig1=(\"@authority\" \"signature-agent\");created=...",
    "signature": "sig1=:...:"
  }
}
fieldrules
pathPath only. /pricing, never /pricing?ref=x and never a full URL. Leaving the query string on makes every distinct URL its own page in the analytics — the most common mistake by far.
methodUppercase. "GET".
statusCodeA number, not a string. 200.
headersLowercase names → verbatim values. This is what classification runs on, so send as many as you can get.

#Headers worth carrying

Roughly in order of value:

headerwhat it buys you
user-agenteverything starts here
fromGPTBot, Googlebot
signature-agent, signature-input, signatureWeb Bot Auth — proves an agent is who it claims to be, rather than taking a User-Agent at face value
accept, accept-languagebrowsers vs */* bots
sec-ch-ua, sec-ch-ua-platform, sec-ch-ua-mobileclient hints — browsers only
sec-fetch-dest, sec-fetch-mode, sec-fetch-sitepresence alone is the signal

Never forward these

authorization, cookie, set-cookie. Both adapters strip them before anything leaves your account. Keep that behaviour if you rewrite one.

#obsero.mjs — the whole contract in one file

Dependency-free, about 150 lines: buildEvent, sendEvent, validateEvent. Run it to send a test event and confirm your tracking ID works before you deploy anything:

node obsero.mjs --token <tracking-id> --domain acme.com

Then, in your own code, this is genuinely all it takes:

the entire integration
import { buildEvent, sendEvent } from "./obsero.mjs";

await sendEvent(
  buildEvent({
    path: req.url,
    method: req.method,
    statusCode: res.statusCode,
    headers: req.headers,
  }),
  { token: process.env.OBSERO_SITE_TOKEN, domain: "acme.com" },
);

If you can call that from your edge middleware, your origin server or your log processor, you are done. You do not need anything else in this repo — and if you are on Cloudflare, Fastly, Akamai or a bare origin, this is the integration. Do not port a cloud directory you do not need.

buildEvent does the two things that are easy to get wrong for you: it strips the query string from path (and tolerates being handed an absolute URL), and it lowercases, trims and filters the headers.

#Check it before you ship it

validateEvent is the machine-checkable form of everything on this page. It returns a list of problems, empty when the payload is good:

cd aws            # or gcp
make test

That runs the real adapter parser over sample log lines and checks each resulting payload against validateEvent. No cloud account needed. If it passes, Obsero will accept what you produce.

#Adapting an adapter

Both adapters have the same three-part shape, and only one part is the contract:

1. THE CONTRACT     build the payload and POST it     <- keep this
2. LOG PARSING      log line/entry -> event           <- yours to rewrite
3. TRANSPORT        unwrap the batch, ack it          <- yours to rewrite

Change parts 2 and 3 to match your log source, leave part 1 alone, then run make test. Fixtures live in each cloud's test/adapter.test.mjs — add one for any new log shape rather than testing against live traffic.

#Getting a tracking ID

Ask Obsero for one, then keep it out of the repo. Both site/terraform/ directories read it from a gitignored terraform.tfvars:

obsero_site_token = "..."

setup.sh writes that file for you, chmod 600. The deployed adapters read x-obsero-domain off each request's own Host header, so there is no domain to configure — only the token.

Code on this page

Was this page helpful?

Reference implementations for streaming CDN request logs to Obsero — a starting point, not a product. Fork it and keep the parts that match your stack. Source on GitHub.

Measured numbers on these pages come from live deployments and are recorded in each cloud's NOTES.md. Confirm current cloud pricing before quoting any cost figure.