Rastree todo.
Comprenda todo.
BetterMeter es una plataforma de analíticas orientada a la privacidad para la era de la IA. Rastree sus sitios web, herramientas CLI, servidores MCP y APIs -- todo desde un solo lugar.
Primeros pasos
BetterMeter rastrea cuatro tipos de fuentes. Cada una utiliza una integración ligera que envía eventos al mismo pipeline respetuoso de la privacidad.
Todas las fuentes pasan por el mismo pipeline: evento -> procesamiento (análisis de referente, detección de bots, geolocalización, hash de privacidad) -> base de datos. Puede ver todo en el dashboard, CLI o mediante herramientas MCP.
Rastreo web
Agregue una sola etiqueta de script a su sitio. Sin cookies, sin configuración CNAME, sin instalación compleja. ~1,3 KB comprimido.
<script defer data-site="example.com" src="https://bettermeter.com/api/script"></script>Opcional: agregue un pixel de rastreo sin JS antes de la etiqueta de cierre </body> para detectar bots y rastreadores que no ejecutan JavaScript:
<img src="https://bettermeter.com/api/pixel?s=example.com" alt="" style="position:absolute;width:0;height:0;overflow:hidden" />El script de rastreo captura automáticamente las vistas de página, la navegación SPA (History API) y los retornos de pestañas. Para eventos personalizados e identificación de usuarios:
// Track custom events
window.bettermeter.track("signup", { plan: "pro" });
// Identify users (optional)
window.bettermeter.identify("user_123");Atributos
data-siterequeridodata-apidata-no-heartbeatDetección de bots del lado del servidor
La mayoría de los bots (Googlebot, GPTBot, ClaudeBot, etc.) no ejecutan JavaScript, por lo que el script de rastreo nunca se activa para ellos. Para detectar tráfico de bots y rastreadores, agregue una línea a su middleware de Next.js:
import { reportBotVisit } from "@bettermeter/node/middleware";
export function middleware(request) {
reportBotVisit(request, "example.com");
// ... rest of your middleware
}Instalar el SDK:
npm install @bettermeter/nodeCompatible con edge, no bloqueante, sin impacto en la latencia. Detecta más de 25 bots conocidos, incluyendo rastreadores de IA, motores de búsqueda y servicios de monitoreo. Las visitas de bots aparecen automáticamente en su dashboard de Crawlers.
Rastreo de CLI
Rastree cómo los desarrolladores usan su herramienta CLI. Vea qué comandos son populares, cuánto tiempo toman y qué errores ocurren -- sin recopilar datos personales.
npm install @bettermeter/nodeimport { BetterMeter } from "@bettermeter/node";
import { Command } from "commander";
const bm = new BetterMeter({
siteId: "my-cli-tool",
apiKey: "bm_...",
});
const program = new Command();
// Wraps all commands — tracks name, flags, exit code
bm.wrapCommander(program, { version: "1.0.0" });
program.command("deploy").action(() => { /* ... */ });
program.parse();
// Flush on exit
process.on("SIGTERM", () => bm.shutdown());bm.trackCommand({
command: "deploy",
subcommand: "preview",
flags: ["--prod", "--verbose"],
version: "2.1.0",
durationMs: 4500,
exitCode: 0,
isCi: !!process.env.CI,
});Qué se rastrea
Solo nombres de comandos y de flags. Los valores de flags, argumentos y rutas de archivos nunca se envían. El SDK captura el sistema operativo y la arquitectura para analíticas de entorno.
Rastreo de MCP
Rastree qué herramientas llaman los clientes de IA en su servidor MCP, con qué frecuencia, qué clientes (Claude, Cursor, Windsurf) generan más uso y las tasas de error.
import { BetterMeter } from "@bettermeter/node";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const bm = new BetterMeter({
siteId: "my-mcp-server",
apiKey: "bm_...",
});
const server = new McpServer({
name: "my-server",
version: "1.0.0",
});
// Wraps server.tool() — auto-tracks every invocation
bm.wrapMcpServer(server);
// Tools registered after wrapping are automatically tracked
server.tool("search_docs", { query: z.string() }, async (args) => {
// ... your tool logic (unchanged)
});bm.trackTool({
tool: "search_docs",
client: "claude-code",
protocolVersion: "2024-11-05",
durationMs: 250,
success: true,
inputTokens: 150,
outputTokens: 800,
});Qué se rastrea
Nombres de herramientas, nombres de clientes, duración, éxito/fallo y conteos opcionales de tokens. Los parámetros de entrada y el contenido de salida de las herramientas nunca se envían.
Rastreo de API
Rastree el uso de endpoints API, latencia, tasas de error y patrones de uso con middleware Express o llamadas manuales.
import { BetterMeter } from "@bettermeter/node";
import express from "express";
const bm = new BetterMeter({
siteId: "my-api",
apiKey: "bm_...",
});
const app = express();
// Auto-track all requests
app.use(bm.expressMiddleware());bm.trackApi({
method: "POST",
endpoint: "/api/users", // Use patterns, not actual paths with IDs
statusCode: 201,
durationMs: 45,
});Qué se rastrea
Método HTTP, patrón de endpoint, código de estado y duración. Los cuerpos de solicitud/respuesta, encabezados, parámetros de consulta y parámetros de ruta nunca se envían. Use patrones de endpoint (/api/users/:id) no rutas reales (/api/users/abc123).
Referencia del SDK
El SDK @bettermeter/node no tiene dependencias (usa fetch y crypto integrados). Requiere Node.js 18+.
Constructor
const bm = new BetterMeter(config);siteIdrequeridoapiKeyrequeridoapiUrldisabledbatchbatchIntervaltrackCommand(options)
commandrequeridosubcommandflagsversiondurationMsexitCodeisCiuserIdpropertiestrackTool(options)
toolrequeridoclientprotocolVersiondurationMssuccesserrorTypeinputTokensoutputTokensuserIdpropertiestrackApi(options)
methodrequeridoendpointrequeridostatusCodedurationMsuserIdpropertiesEncapsuladores automáticos
wrapCommander(program, options?)
Se conecta al postAction de Commander.js para rastrear automáticamente todos los comandos.
wrapMcpServer(server)
Parche dinámico de server.tool() para encapsular todos los manejadores con cronometraje y rastreo de errores.
expressMiddleware()
Devuelve un middleware Express/Connect que rastrea cada solicitud en res.end.
Detección de bots del lado del servidor
La mayoría de los bots no ejecutan JavaScript, por lo que el script de rastreo nunca se activa para ellos. Use reportBotVisit() en su middleware de servidor para detectar bots a nivel de solicitud.
// Next.js middleware
import { reportBotVisit } from "@bettermeter/node/middleware";
export function middleware(request) {
reportBotVisit(request, "my-site.com");
// ... rest of your middleware
}requestrequeridositeIdrequeridooptions.apiUrlCompatible con edge (sin dependencias de Node.js). No bloqueante -- no agrega latencia a las respuestas. Omite automáticamente recursos estáticos, rutas de API e internos de Next.js.
Ciclo de vida
flush(): Promise<void>
Enviar todos los eventos en cola inmediatamente.
shutdown(): Promise<void>
Detener el temporizador de lotes y vaciar los eventos restantes. Llamar antes de que finalice el proceso.
Referencia de CLI
El CLI de BetterMeter le permite consultar analíticas desde la terminal con una salida visual elegante. Todos los comandos aceptan -s/--site, -r/--range, -l/--limit y --json.
npm install -g bettermeter
bettermeter login -t <apiKey> -u <dashboardUrl>Formatos de salida
Por defecto, el CLI muestra una salida visual enriquecida con gráficos ASCII, texto con color, sparklines y tarjetas de estadísticas. Toda la salida usa caracteres Unicode compatibles con todas las terminales modernas. Los colores detectan automáticamente las capacidades de la terminal y respetan la variable de entorno NO_COLOR.
Por defecto (visual)Gráficos de líneas, barras horizontales, sparklines, tablas estilizadas con color--jsonDatos JSON sin procesar -- ideal para scripts, piping a jq o uso programáticoLa salida visual incluye:
- Gráficos de líneas para datos de series temporales (visitantes diarios, invocaciones)
- Barras horizontales para listas ordenadas (páginas, fuentes, países)
- Sparklines junto a las estadísticas generales para una visualización rápida de tendencias
- Tarjetas de estadísticas con indicadores de cambio a color para resúmenes
- Tablas estilizadas con bordes para datos detallados
# Visual output (default)
bettermeter stats -s example.com
# JSON output for scripting
bettermeter stats -s example.com --json | jq '.visitors'Autenticación
login -t <key> -u <url>Autenticarse con clave API y URL del dashboardlogoutEliminar credenciales almacenadaswhoamiMostrar el usuario autenticado actualTiempo real
live -s <siteId>Conteo de visitantes en vivo (--json para salida sin procesar)Analíticas web
statsResumen: visitantes, vistas de página, sesiones + % de cambiopagesPáginas más visitadas por número de visitantessourcesFuentes de tráfico (--filter all|ai|traditional)ai-trafficDesglose de referencias de IA por plataformabotsTráfico de bots/rastreadores (--category all|ai-crawler|search|monitoring|scraper)timeseriesTendencia diaria de visitantes/vistas de páginacountriesVisitantes por paísdevicesDesglose por dispositivobrowsersDesglose por navegadorvisitorsVisitantes recientes con resumen de actividadeventsEventos personalizados con conteoscampaignsAtribución de URL de campaña (UTM + IDs de clic)channelsDesglose por canal (Directo, Búsqueda pagada, Orgánico, etc.)exportReporte completo (--format json|csv|md)Analíticas de CLI
cli-overviewInvocaciones, usuarios, tasa de éxito, duración promediocli-commandsComandos más usados por número de invocacionescli-timeseriesActividad diaria de CLIAnalíticas de MCP
mcp-overviewInvocaciones, usuarios, tasa de éxito, duración promediomcp-toolsHerramientas MCP más usadas por número de invocacionesmcp-clientsDesglose por cliente (Claude, Cursor, etc.)mcp-timeseriesActividad diaria de MCPAnalíticas de API
api-overviewInvocaciones, usuarios, tasa de error, duración promedioapi-endpointsEndpoints más usados por número de invocacionesapi-timeseriesActividad diaria de APIPulse AI
pulse insightsAnomalías, tendencias, oportunidades, hitospulse healthPuntuación de salud del producto (0-100, nota A-F)pulse briefingBriefing diario/semanal (-p/--period daily|weekly)pulse forecastPronóstico de tráfico (-m/--metric, -d/--days)pulse compareComparar dos períodos (-r/--range, --from2, --to2)pulse alertsListar reglas de alerta de monitoreopulse alerts:createCrear alerta (-t/--type, -n/--name, -c/--condition)pulse alerts:deleteEliminar una alerta (-i/--id)pulse notificationsNotificaciones recientes (--unread solo para no leídas)Posicionamiento en búsqueda y visibilidad IA
brand-report <domain>Generar reporte de posicionamiento en búsqueda (-q/--queries)brand-config <domain>Ver/actualizar configuración de monitoreo de marcabrand-compare <domain>Comparar posicionamiento con competidores (-q, -c)brand-alerts <domain>Gestionar alertas de posicionamiento (-a list|create|delete)ai-mentions <domain>Menciones de marca por chatbots de IA (-q/--queries, -p/--providers)backlinks <domain>Perfil de backlinks: rango del dominio, dominios referentesGestión de sitios
sites listListar todos los sitiossites add <domain>Agregar un nuevo sitio (-n/--name para nombre de visualización)sites remove <siteId>Eliminar un sitiosites info <siteId>Mostrar detalles del sitio y código de rastreoinstall <siteId>Obtener código de rastreo para un sitioGestión de equipo
members list -s <siteId>Listar miembros del sitiomembers add <email>Agregar un miembro (-s, -r viewer|editor|admin, --all-sites)members remove <id>Eliminar un miembro (-s)members update-role <id>Actualizar rol de miembro (-s, -r)Facturación
billingMostrar plan actual, uso e información de facturación (--json)Opciones
-s, --siterequerido-r, --range-l, --limit--jsonReferencia de herramientas MCP
BetterMeter funciona como un servidor MCP para asistentes de IA. Cada comando CLI tiene una herramienta MCP correspondiente con el prefijo bettermeter_.
{
"mcpServers": {
"bettermeter": {
"command": "npx",
"args": ["bettermeter"]
}
}
}Todas las herramientas de analíticas aceptan site_id (string), range (today|7d|30d|90d|12m) y limit (number). La herramienta MCP bettermeter_export soporta los formatos json y md (el comando CLI export también soporta csv). Ejemplos de uso para asistentes de IA:
bettermeter_live_visitorsbettermeter_statsbettermeter_ai_trafficbettermeter_cli_commandsbettermeter_mcp_clientsbettermeter_api_endpoints with range=7dbettermeter_pulse_healthbettermeter_pulse_insightsbettermeter_pulse_briefing with period=weeklyLista completa de herramientas
Tiempo real
bettermeter_live_visitorsAnalíticas web
bettermeter_statsbettermeter_pagesbettermeter_sourcesbettermeter_ai_trafficbettermeter_botsbettermeter_timeseriesbettermeter_countriesbettermeter_devicesbettermeter_browsersbettermeter_visitorsbettermeter_visitorbettermeter_eventsbettermeter_campaignsbettermeter_channelsbettermeter_exportAnalíticas de CLI
bettermeter_cli_overviewbettermeter_cli_commandsbettermeter_cli_timeseriesAnalíticas de MCP
bettermeter_mcp_overviewbettermeter_mcp_toolsbettermeter_mcp_clientsbettermeter_mcp_timeseriesAnalíticas de API
bettermeter_api_overviewbettermeter_api_endpointsbettermeter_api_timeseriesGestión de sitios
bettermeter_sites_listbettermeter_sites_addbettermeter_sites_removebettermeter_sites_infobettermeter_installGestión de equipo
bettermeter_members_listbettermeter_members_addbettermeter_members_removebettermeter_members_update_rolePosicionamiento en búsqueda y visibilidad IA
bettermeter_brand_reportbettermeter_brand_configbettermeter_brand_comparebettermeter_brand_alertsbettermeter_ai_mentionsbettermeter_backlinksFacturación
bettermeter_billingPulse AI
bettermeter_pulse_insightsbettermeter_pulse_healthbettermeter_pulse_briefingbettermeter_pulse_forecastbettermeter_pulse_comparebettermeter_pulse_alerts_listbettermeter_pulse_alerts_createbettermeter_pulse_alerts_deletebettermeter_pulse_notificationsReferencia de API
Todos los endpoints de analíticas aceptan solicitudes GET con parámetros de consulta. Autentíquese con Authorization: Bearer <api_key>.
Ingestión de eventos
POST /api/eventIngerir un evento (web, CLI, MCP o API). Devuelve 202.{
"site_id": "example.com",
"event_name": "pageview", // or "cli.command", "mcp.tool", "api.request"
"event_source": "web", // "web" | "cli" | "mcp" | "api"
"url": "https://example.com/page",
"pathname": "/page",
"hostname": "example.com",
"referrer": "https://google.com",
"screen_width": 1920,
"timezone": "America/New_York",
"user_id": "optional_user_id",
"properties": { "key": "value" }
}Tiempo real y heartbeat
GET /api/analytics/liveConteo de visitantes en vivo. Devuelve { live: number }. Acepta ?siteId=...POST /api/heartbeatRecibir heartbeats del navegador para rastreo de visitantes en vivoPOST /api/hAlias sigiloso para /api/heartbeat (resistente a bloqueadores de anuncios)Endpoints de consulta
Todos aceptan ?siteId=...&from=YYYY-MM-DD&to=YYYY-MM-DD.
Analíticas web
GET /api/analytics/overviewVisitantes, vistas de página, sesiones + % de cambioGET /api/analytics/pagesPáginas más visitadas por número de visitantesGET /api/analytics/sourcesFuentes de tráfico con detección de IAGET /api/analytics/timeseriesTendencia diaria de visitantes/vistas de páginaGET /api/analytics/ai-trafficDesglose de referencias de IA por plataformaGET /api/analytics/botsTráfico de bots/rastreadoresGET /api/analytics/countriesVisitantes por paísGET /api/analytics/devicesDesglose por tipo de dispositivoGET /api/analytics/browsersDesglose por navegadorGET /api/analytics/visitorsLista de visitantes con actividadGET /api/analytics/visitors/[visitorId]Perfil del visitante con cronología de eventosGET /api/analytics/eventsEventos personalizadosGET /api/analytics/campaignsAtribución de URL de campaña (UTM + IDs de clic)GET /api/analytics/campaigns/[campaign]Detalle de una campaña individualGET /api/analytics/channelsDesglose por canalGET /api/analytics/session-statsEstadísticas de sesionesGET /api/analytics/sessionsLista de sesionesAnalíticas de CLI
GET /api/analytics/cli-overviewInvocaciones, usuarios, tasa de éxitoGET /api/analytics/cli-commandsComandos más usadosGET /api/analytics/cli-timeseriesActividad diaria de CLIAnalíticas de MCP
GET /api/analytics/mcp-overviewInvocaciones, usuarios, tasa de éxitoGET /api/analytics/mcp-toolsHerramientas más usadasGET /api/analytics/mcp-clientsDesglose por clienteGET /api/analytics/mcp-timeseriesActividad diaria de MCPAnalíticas de API
GET /api/analytics/api-overviewInvocaciones, usuarios, tasa de errorGET /api/analytics/api-endpointsEndpoints más usadosGET /api/analytics/api-timeseriesActividad diaria de APIPosicionamiento en búsqueda y visibilidad IA
GET /api/analytics/brandReporte de visibilidad en posicionamiento de búsquedaGET /api/analytics/brand/historyDatos históricos de posicionamientoGET /api/analytics/brand/compareComparación con competidoresGET /api/analytics/brand/alertsReglas de alerta de posicionamientoGET /api/analytics/brand/exportExportar datos de posicionamientoGET /api/analytics/backlinksPerfil de backlinksGET /api/analytics/ai-mentionsMenciones de marca por chatbots de IAGET /api/analytics/ai-mentions/historyHistorial de menciones de IAPulse AI
GET /api/pulse/insightsAnomalías, tendencias, oportunidadesGET /api/pulse/healthPuntuación de salud del producto (0-100)GET /api/pulse/briefingBriefing diario o semanalGET /api/pulse/forecastPronóstico de tráfico/usoGET /api/pulse/compareComparación de períodosGET /api/pulse/alertsListar alertas de monitoreoPOST /api/pulse/alertsCrear regla de alertaDELETE /api/pulse/alertsEliminar regla de alertaGET /api/pulse/notificationsObtener notificacionesPATCH /api/pulse/notificationsMarcar notificaciones como leídasPOST /api/pulse/chatChat Pulse AI (streaming)Pulse AI
Pulse AI es la capa de inteligencia analítica proactiva de BetterMeter. En lugar de esperar a que revise los dashboards, Pulse detecta automáticamente anomalías, muestra insights, evalúa la salud del producto, pronostica tendencias y entrega briefings diarios o semanales.
Capacidades
Tipos de alertas
Cree alertas de monitoreo que generan notificaciones cuando se cumplen las condiciones:
traffic_spikeAlerta cuando el tráfico supera un umbraltraffic_dropAlerta cuando el tráfico cae por debajo de un umbralerror_rateAlerta cuando la tasa de error supera un umbralbot_surgeAlerta por picos inusuales de actividad de bots/rastreadoresnew_ai_crawlerAlerta cuando se detecta un nuevo rastreador de IAperformanceAlerta por degradación del rendimientoEndpoints de API
GET /api/pulse/insightsAnomalías, tendencias, oportunidades, hitosGET /api/pulse/healthPuntuación de salud del producto (0-100) con desglose por categoríaGET /api/pulse/briefingResumen de briefing diario o semanalGET /api/pulse/forecastPronóstico de tráfico/uso con intervalos de confianzaGET /api/pulse/compareComparación detallada período por períodoGET /api/pulse/alertsListar reglas de alerta de monitoreoPOST /api/pulse/alertsCrear una nueva regla de alertaDELETE /api/pulse/alertsEliminar una regla de alertaGET /api/pulse/notificationsObtener notificaciones recientesPATCH /api/pulse/notificationsMarcar notificaciones como leídasComandos CLI
pulse insightsObtener insights proactivos y anomalíaspulse healthPuntuación de salud del producto con notapulse briefingBriefing diario o semanal (-p/--period daily|weekly)pulse forecastPronóstico de tráfico (-m/--metric, -d/--days)pulse compareComparar dos períodos (-r/--range, --from2, --to2)pulse alertsListar alertas de monitoreopulse alerts:createCrear alerta (-t/--type, -n/--name, -c/--condition)pulse alerts:deleteEliminar una alerta (-i/--id)pulse notificationsNotificaciones recientes (--unread solo para no leídas)Herramientas MCP
bettermeter_pulse_insightsbettermeter_pulse_healthbettermeter_pulse_briefingbettermeter_pulse_forecastbettermeter_pulse_comparebettermeter_pulse_alerts_listbettermeter_pulse_alerts_createbettermeter_pulse_alerts_deletebettermeter_pulse_notificationsConfiguración de proxy
Los bloqueadores de anuncios a veces bloquean scripts de analíticas de terceros. Al hacer proxy de BetterMeter a través de su propio dominio, el script y los eventos aparecen como solicitudes de primera parte -- haciéndolos invisibles para los bloqueadores. Esto funciona porque el navegador ve solicitudes a yourdomain.com/bm/... en lugar de bettermeter.com/api/....
Cómo funciona
Configure reescrituras de URL en su servidor para que tres rutas de su dominio redirijan a BetterMeter:
/bm/s→ https://bettermeter.com/api/sScript de rastreo/bm/e→ https://bettermeter.com/api/eEndpoint de eventos/bm/p→ https://bettermeter.com/api/pPixel noscriptLuego actualice su código de rastreo para usar las rutas del proxy:
<script defer data-site="example.com" data-api="/bm/e" src="/bm/s"></script>
<img src="/bm/p?s=example.com" alt="" style="position:absolute;width:0;height:0;overflow:hidden" />El atributo data-api le indica al script que envíe eventos a su endpoint proxy en lugar de directamente a BetterMeter. El src carga el script desde su proxy. Para el navegador (y los bloqueadores de anuncios), todo es una solicitud del mismo origen.
Guías por plataforma
Next.js
module.exports = {
async rewrites() {
return [
{ source: "/bm/s", destination: "https://bettermeter.com/api/s" },
{ source: "/bm/e", destination: "https://bettermeter.com/api/e" },
{ source: "/bm/p", destination: "https://bettermeter.com/api/p" },
];
},
};Nuxt
export default defineNuxtConfig({
routeRules: {
"/bm/s": { proxy: "https://bettermeter.com/api/s" },
"/bm/e": { proxy: "https://bettermeter.com/api/e" },
"/bm/p": { proxy: "https://bettermeter.com/api/p" },
},
});SvelteKit
const config = {
kit: {
// SvelteKit doesn't have built-in rewrites.
// Use a server hook instead:
},
};import type { Handle } from "@sveltejs/kit";
const PROXY_MAP: Record<string, string> = {
"/bm/s": "https://bettermeter.com/api/s",
"/bm/e": "https://bettermeter.com/api/e",
"/bm/p": "https://bettermeter.com/api/p",
};
export const handle: Handle = async ({ event, resolve }) => {
const target = PROXY_MAP[event.url.pathname];
if (target) {
const url = new URL(target);
url.search = event.url.search;
const res = await fetch(url, {
method: event.request.method,
headers: event.request.headers,
body: event.request.method !== "GET" ? event.request.body : undefined,
// @ts-expect-error — needed for streaming
duplex: "half",
});
return new Response(res.body, {
status: res.status,
headers: res.headers,
});
}
return resolve(event);
};Astro
import type { APIRoute } from "astro";
const PROXY_MAP: Record<string, string> = {
s: "https://bettermeter.com/api/s",
e: "https://bettermeter.com/api/e",
p: "https://bettermeter.com/api/p",
};
export const ALL: APIRoute = async ({ params, request }) => {
const target = PROXY_MAP[params.proxy ?? ""];
if (!target) return new Response("Not found", { status: 404 });
const url = new URL(target);
url.search = new URL(request.url).search;
return fetch(url, {
method: request.method,
headers: request.headers,
body: request.method !== "GET" ? request.body : undefined,
// @ts-expect-error — needed for streaming
duplex: "half",
});
};Remix / React Router
import type { LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node";
const PROXY_MAP: Record<string, string> = {
s: "https://bettermeter.com/api/s",
e: "https://bettermeter.com/api/e",
p: "https://bettermeter.com/api/p",
};
async function proxy({ request, params }: LoaderFunctionArgs | ActionFunctionArgs) {
const target = PROXY_MAP[params["*"] ?? ""];
if (!target) return new Response("Not found", { status: 404 });
const url = new URL(target);
url.search = new URL(request.url).search;
return fetch(url, {
method: request.method,
headers: request.headers,
body: request.method !== "GET" ? request.body : undefined,
// @ts-expect-error — needed for streaming
duplex: "half",
});
}
export const loader = proxy;
export const action = proxy;Nginx
location /bm/s {
proxy_pass https://bettermeter.com/api/s;
proxy_ssl_server_name on;
proxy_set_header Host bettermeter.com;
}
location /bm/e {
proxy_pass https://bettermeter.com/api/e;
proxy_ssl_server_name on;
proxy_set_header Host bettermeter.com;
}
location /bm/p {
proxy_pass https://bettermeter.com/api/p;
proxy_ssl_server_name on;
proxy_set_header Host bettermeter.com;
}Apache
RewriteEngine On
RewriteRule ^bm/s$ https://bettermeter.com/api/s [P,L]
RewriteRule ^bm/e$ https://bettermeter.com/api/e [P,L]
RewriteRule ^bm/p$ https://bettermeter.com/api/p [P,L]
# Requires mod_proxy and mod_proxy_http enabled:
# a2enmod proxy proxy_httpCaddy
example.com {
handle_path /bm/s {
reverse_proxy https://bettermeter.com/api/s {
header_up Host bettermeter.com
}
}
handle_path /bm/e {
reverse_proxy https://bettermeter.com/api/e {
header_up Host bettermeter.com
}
}
handle_path /bm/p {
reverse_proxy https://bettermeter.com/api/p {
header_up Host bettermeter.com
}
}
}Cloudflare Workers
export default {
async fetch(request) {
const url = new URL(request.url);
const map = {
"/bm/s": "https://bettermeter.com/api/s",
"/bm/e": "https://bettermeter.com/api/e",
"/bm/p": "https://bettermeter.com/api/p",
};
const target = map[url.pathname];
if (!target) return fetch(request);
const dest = new URL(target);
dest.search = url.search;
return fetch(dest.toString(), {
method: request.method,
headers: { ...Object.fromEntries(request.headers), Host: "bettermeter.com" },
body: request.method !== "GET" ? request.body : undefined,
});
},
};Vercel (without Next.js)
{
"rewrites": [
{ "source": "/bm/s", "destination": "https://bettermeter.com/api/s" },
{ "source": "/bm/e", "destination": "https://bettermeter.com/api/e" },
{ "source": "/bm/p", "destination": "https://bettermeter.com/api/p" }
]
}Netlify
[[redirects]]
from = "/bm/s"
to = "https://bettermeter.com/api/s"
status = 200
force = true
[[redirects]]
from = "/bm/e"
to = "https://bettermeter.com/api/e"
status = 200
force = true
[[redirects]]
from = "/bm/p"
to = "https://bettermeter.com/api/p"
status = 200
force = trueWordPress
Agregue estas reglas de reescritura al functions.php de su tema o a un plugin personalizado:
add_action('init', function() {
add_rewrite_rule('^bm/s$', 'index.php?bm_proxy=s', 'top');
add_rewrite_rule('^bm/e$', 'index.php?bm_proxy=e', 'top');
add_rewrite_rule('^bm/p$', 'index.php?bm_proxy=p', 'top');
});
add_filter('query_vars', function($vars) {
$vars[] = 'bm_proxy';
return $vars;
});
add_action('template_redirect', function() {
$proxy = get_query_var('bm_proxy');
if (!$proxy) return;
$map = [
's' => 'https://bettermeter.com/api/s',
'e' => 'https://bettermeter.com/api/e',
'p' => 'https://bettermeter.com/api/p',
];
if (!isset($map[$proxy])) return;
$url = $map[$proxy];
if ($_SERVER['QUERY_STRING']) $url .= '?' . $_SERVER['QUERY_STRING'];
$args = ['method' => $_SERVER['REQUEST_METHOD'], 'timeout' => 10];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$args['body'] = file_get_contents('php://input');
$args['headers'] = ['Content-Type' => $_SERVER['CONTENT_TYPE'] ?? 'application/json'];
}
$response = wp_remote_request($url, $args);
if (is_wp_error($response)) { status_header(502); exit; }
status_header(wp_remote_retrieve_response_code($response));
foreach (wp_remote_retrieve_headers($response) as $k => $v) {
if (in_array(strtolower($k), ['content-type', 'cache-control'])) header("$k: $v");
}
echo wp_remote_retrieve_body($response);
exit;
});
// After adding, flush rewrite rules: Settings → Permalinks → SaveDjango
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path("bm/<slug:endpoint>", views.bm_proxy),
]
# views.py
import requests
from django.http import HttpResponse
PROXY_MAP = {
"s": "https://bettermeter.com/api/s",
"e": "https://bettermeter.com/api/e",
"p": "https://bettermeter.com/api/p",
}
def bm_proxy(request, endpoint):
target = PROXY_MAP.get(endpoint)
if not target:
return HttpResponse("Not found", status=404)
url = target + ("?" + request.META["QUERY_STRING"] if request.META.get("QUERY_STRING") else "")
resp = requests.request(
method=request.method, url=url,
headers={"Host": "bettermeter.com", "Content-Type": request.content_type},
data=request.body if request.method == "POST" else None,
timeout=10,
)
return HttpResponse(resp.content, status=resp.status_code, content_type=resp.headers.get("Content-Type"))Rails
# config/routes.rb
match "/bm/:endpoint", to: "bm_proxy#proxy", via: [:get, :post], constraints: { endpoint: /[sep]/ }
# app/controllers/bm_proxy_controller.rb
class BmProxyController < ApplicationController
skip_before_action :verify_authenticity_token
PROXY_MAP = { "s" => "/api/s", "e" => "/api/e", "p" => "/api/p" }.freeze
def proxy
path = PROXY_MAP[params[:endpoint]]
return head(:not_found) unless path
uri = URI("https://bettermeter.com#{path}")
uri.query = request.query_string.presence
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
req = (request.post? ? Net::HTTP::Post : Net::HTTP::Get).new(uri)
req["Host"] = "bettermeter.com"
if request.post?
req.body = request.body.read
req["Content-Type"] = request.content_type
end
resp = http.request(req)
render body: resp.body, status: resp.code.to_i, content_type: resp["Content-Type"]
end
endVerificación
Después de configurar el proxy, verifique que funcione:
# Should return the tracker JavaScript
curl -s https://yourdomain.com/bm/s | head -5
# Should return 202 (event accepted)
curl -s -o /dev/null -w "%{http_code}" -X POST https://yourdomain.com/bm/e \
-H "Content-Type: application/json" \
-d '{"site_id":"yourdomain.com","event_name":"test"}'Modelo de datos
Las cuatro fuentes de eventos comparten una sola tabla Event. La columna eventSource las distingue. Los metadatos específicos de cada fuente están en la columna JSON properties.
Mapeo de campos
Privacidad
BetterMeter está diseñado desde cero para respetar la privacidad del usuario. No se necesitan banners de consentimiento. Cumple con GDPR por defecto.
Sin cookies
Cero cookies, cero almacenamiento local, cero huellas digitales. El script de rastreo no tiene estado.
Identificadores hasheados
Las direcciones IP se hashean con SHA-256 con la fecha actual incluida en la entrada del hash. La IP sin procesar nunca se almacena. Los hashes de visitantes diarios rotan cada 24 horas. Un ID de visitante estable (hasheado sin fecha) permite perfiles de visitantes dentro de un sitio pero no puede revertirse para revelar la IP original.
Sin recopilación de datos personales
El SDK rastrea nombres de comandos, nombres de herramientas y patrones de endpoints -- nunca argumentos, rutas de archivos, cuerpos de solicitud ni datos personales.
Enviar y olvidar
Las llamadas de analíticas son asíncronas y nunca lanzan excepciones. Un fallo de red descarta silenciosamente el evento. Las analíticas nunca deben interrumpir la aplicación principal.
Desactivación
Configure disabled: true en la configuración del SDK o BETTERMETER_DISABLED=1 como variable de entorno.
Pipeline abierto
Cada evento pasa por la misma función processEvent(). Puede auditar exactamente qué se recopila y almacena.
BetterMeter Analytics -- Creado para la nueva internet