Skip to main content

stapel-analytics

The backend half of the Stapel analytics standard. Your frontend already declares its events and fires them through @stapel/analytics — typed track(), a consent gate, an offline queue, provider fan-out. This is what it talks to, and what the rest of your fleet talks to.

browser (@stapel/analytics)                 server modules
   │ track / page / identify                    │ analytics.track
   ▼                                            ▼
POST /analytics/api/v1/events  ──►  registry check + PII guard
                                            │
   your comm Actions ──► COMM_BRIDGE ────────┤
   (payment.completed, …)                    │
                                             ▼
                                  stapel_core.eventstore  ("analytics" stream)
                                             │
                    ┌────────────────────────┼────────────────────┐
                    ▼                        ▼                    ▼
              funnels / reports      analytics.events.recorded   erasure
              (conversion by step)   ──► adapter fan-out         (GDPR Art. 17)

Part of the Stapel framework.

Install

pip install stapel-analytics
INSTALLED_APPS = [
    ...,
    "stapel_core.django.eventstore",   # the event rows live here
    "stapel_analytics",
]

path("analytics/", include("stapel_analytics.urls"))   # -> /analytics/api/v1/...

That is the whole install. Point your frontend's collector at it and events start landing — the shipped stapelCollectorProvider needs no changes.

Declare your events once

The registry is the same analytics/events.json your frontend's gen:events already produces:

STAPEL_ANALYTICS = {"EVENTS_FILE": BASE_DIR / "analytics" / "events.json"}

One vocabulary, two runtimes. An event nobody declared is still stored — and marked unregistered, so you find the typo instead of losing the data.

Ask where people stop

STAPEL_ANALYTICS = {
    "FUNNELS": {
        "checkout": {
            "title": "Checkout",
            "steps": ["flow.checkout.started", "checkout.address",
                      "payment_completed"],
            "window_seconds": 86400,
        }
    }
}
GET /analytics/api/v1/funnels/checkout/report?compare=true
{"entered": 1204, "completed": 331, "conversion": 0.274917,
 "steps": [
   {"name": "flow.checkout.started", "count": 1204, "rate_from_first": 1.0},
   {"name": "checkout.address",      "count": 502,  "rate_from_previous": 0.416944,
    "dropoff": 702, "delta": -48},
   {"name": "payment_completed",     "count": 331,  "rate_from_previous": 0.659363,
    "dropoff": 171, "delta": 12}
 ]}

Notice the last step: payment_completed happens on a server. One line of settings makes it a step of the same funnel as the clicks before it:

STAPEL_ANALYTICS = {"COMM_BRIDGE": {"payment.completed": "payment_completed"}}

The user id is hashed exactly the way the browser hashes it, so the server step and the clicks belong to the same person.

Mirror the stream anywhere

STAPEL_ANALYTICS = {
    "ADAPTERS": {
        "webhook": {"enabled": True, "config": {"url": "https://collect…"}},
        "posthog": {"handler": "app.analytics.posthog", "enabled": True},
    }
}

Delivery rides the comm outbox, never the ingest request thread. A vendor's outage costs you nothing: the event store is the record and manage.py analytics_fanout --since … replays the mirror.

Close the loop back to the ad platform

Measuring the click is half of it. The deal it led to closes on the phone a week later, and until that outcome goes back to Google Ads the bidding is optimizing for form submissions instead of for revenue.

pip install "stapel-analytics[google-ads]"
call("analytics.upload_click_conversion", {
    "click_id": "Cj0KCQ…",             # gclid | gbraid | wbraid
    "conversion_action": "customers/1234567890/conversionActions/42",
    "conversion_at": "2026-09-04T11:02:00Z",
    "clicked_at": "2026-08-30T09:14:00Z",    # optional, and load-bearing
    "value": 4900, "currency": "EUR",
})
# -> {"status": "uploaded"}

Durable before it is delivered, and idempotent on (click_id, conversion_action, conversion_at) — the same conversion reported twice is one row and at most one upload. An upload that could not be attempted comes back pending, not rejected: the row waits and manage.py analytics_upload_conversions retries it on a capped backoff. --dry-run lists what would go out and writes nothing at all.

clicked_at is optional because most callers do not have it, and load-bearing because Google's 90-day window is measured from the click. With it, that rule is enforced locally. Without it the module falls back to the conversion's own age — a strictly weaker test, and it says so instead of advertising a guarantee the input cannot support.

Privacy is the default, not a setting you remember

  • prop values that look like an email or a phone number are refused, and the receipt names the offending prop;
  • user ids are stored as a hash, never raw;
  • the erasure provider ships in this same first release — analytics rows are personal data, including the anonymous session someone had before they logged in:
STAPEL_GDPR = {"DATA_OWNERS": [..., "analytics"]}
  • and there is a retention horizon out of the box (400 days), with a system check if you turn it off.

Documentation

  • MODULE.md — the integration contract: wire format, settings, checks, comm surface, erasure policy.
  • CONFIG.MD — every setting, and the four decisions a host actually has to make.
  • CHANGELOG.md — what changed, and what breaks.

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

stapel_analytics-0.4.1.tar.gz (155.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

stapel_analytics-0.4.1-py3-none-any.whl (130.9 kB view details)

Uploaded Python 3

File details

Details for the file stapel_analytics-0.4.1.tar.gz.

File metadata

  • Download URL: stapel_analytics-0.4.1.tar.gz
  • Upload date:
  • Size: 155.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for stapel_analytics-0.4.1.tar.gz
Algorithm Hash digest
SHA256 7a0379c55c0c2348e95ab7c0bed0f51702f1ed97d7f6f149411780b2397d0807
MD5 dfcb57aebafe3e6dde3dd69e199337b7
BLAKE2b-256 9dec11b6a4b20fc7f5010bf6ad6ccfac8fc783c7023ceafef9a094ca7b5a414f

See more details on using hashes here.

Provenance

The following attestation bundles were made for stapel_analytics-0.4.1.tar.gz:

Publisher: publish.yml on usestapel/stapel-analytics

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file stapel_analytics-0.4.1-py3-none-any.whl.

File metadata

File hashes

Hashes for stapel_analytics-0.4.1-py3-none-any.whl
Algorithm Hash digest
SHA256 595ef9c42e6c973621162e47436e0308c8d9af6b3f4b505fd0351a2e4db1e769
MD5 bcad9cd2df59385e51bae1c7a9e536ad
BLAKE2b-256 f8befe3adfc72dd261803773f835fd2a99cb5d61cc725a6bcf65de8ee2143faa

See more details on using hashes here.

Provenance

The following attestation bundles were made for stapel_analytics-0.4.1-py3-none-any.whl:

Publisher: publish.yml on usestapel/stapel-analytics

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.5.3

2 files

0.5.2

2 files

0.5.1

2 files

0.5.0

2 files

This release

0.4.1 This release

2 files

0.4.0

2 files

0.3.2

2 files

0.3.1

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page