Product analytics and observability
Fuel Credit uses two separate Cloudflare analytics products:
- Cloudflare Web Analytics for privacy-first page views and browser performance. It does not accept custom product events.
- Workers Analytics Engine through the dedicated
fuel-credit-product-telemetryWorker for bounded product events.
Custom telemetry is fail-safe and non-blocking. The broad early-access privacy policy is published with operator, global-service, retention, access-role, and privacy-contact details. Telemetry remains disabled until regional consent behavior and automated retention/deletion controls are implemented and verified.
Privacy contract
The browser and Worker must never transmit or store:
- raw wallet addresses;
- transaction IDs, signatures, balances, collateral or repayment amounts;
- typed form values;
- exception messages, stack traces or arbitrary DOM text.
Wallet filtering uses account_tag, a SHA-256 digest of:
fuel-credit-analytics:v1:<network>:<lowercase wallet address>This is pseudonymous data, not anonymous data. Access should be restricted to protocol operators. The tracker honors Do Not Track: 1, uses no cookies, and creates a random per-tab session ID. Mainnet and testnet tags intentionally differ.
Derive the tag for a known address locally:
bun -e 'const [network,address]=process.argv.slice(2); const bytes=new TextEncoder().encode(`fuel-credit-analytics:v1:${network}:${address.toLowerCase()}`); console.log(Buffer.from(await crypto.subtle.digest("SHA-256",bytes)).toString("hex"))' testnet 0xADDRESSEvent dictionary
| Funnel | Events |
|---|---|
| Visit | app_loaded, route_view, network_selected |
| Wallet | wallet_connect_started, wallet_connected, wallet_disconnected |
| Funding | balance_state_detected, faucet_started, faucet_gas_redirected, faucet_claimed, faucet_failed |
| Quote/open | quote_loaded, quote_failed, loan_open_started, loan_open_confirmed, loan_open_failed |
| Account | account_sync_started, account_sync_completed, account_sync_failed |
| Repayment | repayment_started, repayment_confirmed, repayment_failed |
| Maturity | maturity_finalize_started, maturity_finalize_confirmed, maturity_finalize_failed |
| Claim | collateral_claim_started, collateral_claim_confirmed, collateral_claim_failed |
| Friction | validation_blocked, global_error, unhandled_rejection, rage_click |
Errors are reduced locally to an allowlisted category such as user_rejected, insufficient_funds, oracle_unavailable, network_error, maturity_restriction or unknown. A flow_id connects start/success/failure without exposing an onchain transaction ID. Rage clicks require three pointer interactions on the same normalized control within 1.5 seconds and are suppressed for 30 seconds.
Analytics Engine blob layout:
- event
- network
- route
- session ID
- account tag
- target
- outcome
- error code
- flow ID
Double layout: occurrence timestamp, duration in milliseconds. The indexed dimension is <network>:<account_tag|anonymous>.
Activation
Publish a privacy disclosure and choose/document a retention period and access owners.
Enable Cloudflare Web Analytics for
fuel.creditin Cloudflare Dashboard → Web Analytics and add its generated beacon token toapp/index.html(or enable automatic Pages Web Analytics). Do not send custom events to that beacon.Review
workers/telemetry/wrangler.jsonc. Production permits onlyhttps://fuel.credit. Add a preview URL only as an explicit comma-separated origin during a controlled preview deployment; never use*.Deploy the custom endpoint:
bashbun run telemetry:deployChange the Worker
TELEMETRY_ENABLEDvariable totrue, deploy it, build the app with both variables, and deploy the app:bashVITE_ANALYTICS_ENABLED=true \ VITE_ANALYTICS_ENDPOINT=https://events.fuel.credit/v1/events \ bunx vite build app
Both flags are emergency stops. Disable the Worker first to stop ingestion immediately; rebuild with VITE_ANALYTICS_ENABLED=false to stop client emission.
Queries
Use the Cloudflare Analytics Engine SQL API or dashboard. Example funnel by network:
SELECT blob2 AS network, blob1 AS event, count() AS events
FROM fuel_credit_product_events_v1
WHERE timestamp > NOW() - INTERVAL '7' DAY
AND blob1 IN ('loan_open_started', 'loan_open_confirmed', 'loan_open_failed')
GROUP BY network, event
ORDER BY network, eventUnresolved errors by category:
SELECT blob2 AS network, blob8 AS error_code, count() AS failures
FROM fuel_credit_product_events_v1
WHERE timestamp > NOW() - INTERVAL '24' HOUR
AND blob7 = 'failed'
GROUP BY network, error_code
ORDER BY failures DESCFilter a known account after deriving its tag:
SELECT timestamp, blob1 AS event, blob3 AS route, blob7 AS outcome, blob8 AS error_code
FROM fuel_credit_product_events_v1
WHERE blob5 = '<account_tag>'
ORDER BY timestamp DESC
LIMIT 200Operations
- Review aggregate funnels; do not use account-level data for marketing or profiling.
- Never log rejected request bodies. The Worker responds only with fixed error codes.
- The endpoint accepts at most 20 events and 32 KiB per request. Unknown fields, origins, events, routes and unbounded strings are rejected.
- Revisit the allowlist and event dictionary before adding any event. Prefer a new fixed category over arbitrary metadata.
- Cloudflare Worker observability is separate from product telemetry and should retain sampled structured service errors only.