VisitorPing Analytics API v1

Read aggregate analytics for a website already connected to VisitorPing. No additional tracker or duplicate event pipeline is required. This API is read-only and server-to-server.

Pro or Agency required. Creating and using API keys requires an active eligible plan. Starter, including the Starter trial, does not include API access. Every request rechecks eligibility; existing keys stop working after a downgrade or loss of subscription access. Owners and admins can still list and revoke keys. View plans

Create a key

Workspace owners/admins open Settings → Manage analytics API keys (/dashboard/settings/api-keys). Choose a website, name, and expiry (30, 90, or 365 days). Copy the secret when it appears; only a SHA-256 hash and non-secret prefix are stored. Revocation takes effect on the next authenticated request. To rotate, create a replacement, update your application, then revoke the old key. Each key reads one website; create separate keys for separate websites.

Keep secrets in server environment variables. Never embed them in frontend JavaScript, URLs, screenshots, or public repositories. API authentication does not accept a browser session or the public tracker site key. There is no browser CORS grant. Your backend can expose selected aggregate results to your own frontend.

Requests

Base URL: https://visitorping.com/api/v1

Send Authorization: Bearer YOUR_API_KEY. All endpoints are GET-only.

EndpointResult
/sitesThe website accessible to this key: id, name, domain
/sites/{id}/analyticsTotals and UTC time series
/sites/{id}/breakdownsAggregate rows by country, city, source, page, or device
/sites/{id}/eventsAggregate rows by event name

Parameters for analytics, breakdowns, and events

Metric definitions

Every metric is based on event occurrence time, not the session's start date. Sessions that began before the requested window count if they contain a matching event in the window.

Response envelopes

GET /sites returns { apiVersion: "1", data: [{ id, name, domain }], generatedAt }.

The other endpoints return:

{
  "apiVersion": "1",
  "site": { "id": "SITE_ID", "name": "My site", "domain": "example.com" },
  "range": { "from": "2026-09-11T00:00:00.000Z", "to": "2026-09-11T12:00:00.000Z", "timezone": "UTC", "interval": "day" },
  "traffic": "exclude_bots",
  "generatedAt": "2026-09-11T12:00:01.000Z",
  "lastEventAt": "2026-09-11T11:59:58.000Z",
  "historyDays": 30,
  "freshness": "Eventually consistent. lastEventAt is site activity, not a completeness watermark.",
  "data": {
    "totals": { "events": 12, "uniqueVisitors": 3, "sessions": 4, "pageviews": 6 },
    "series": [{ "bucket": "2026-09-11T00:00:00Z", "events": 12, "uniqueVisitors": 3, "sessions": 4, "pageviews": 6 }]
  }
}

Illustrative numbers only. filter is also present when an event filter is used. Breakdowns/events replace data with { dimension, rows: [{ value, events, uniqueVisitors, sessions, pageviews }], truncated }. range.interval appears only for analytics. lastEventAt may be null.

Results are eventually consistent as queued tracking events arrive. generatedAt records request generation time, not ingestion completeness. lastEventAt is the site's last recorded activity, not a promise that all earlier events have arrived. Retry reads to incorporate late events. No live stream or aggregate-push subscription is included in this v1; the existing alert webhooks are a separate feature.

Server example

const response = await fetch(
  `https://visitorping.com/api/v1/sites/${encodeURIComponent(process.env.VISITORPING_SITE_ID)}/analytics`,
  { headers: { Authorization: `Bearer ${process.env.VISITORPING_API_KEY}` }, cache: "no-store" }
);
if (!response.ok) throw new Error(`VisitorPing API returned ${response.status}`);
const analytics = await response.json();
// Return only the aggregates your public UI needs, never the API key.

Custom-event example (Take The Wall or any other application)

The existing browser tracker supports:

window.VisitorPing?.track("wall_impression", { takeoverId: "YOUR_TAKEOVER_ID" });
window.VisitorPing?.track("wall_owner_link_click", { takeoverId: "YOUR_TAKEOVER_ID" });

Emit impressions only when the ad becomes visible, not on every render. Attach application context explicitly; automatic tracking cannot infer which owner is currently displayed. Record clicks without blocking navigation. No buyer email or sensitive fields belong in analytics.

Query analytics?event=wall_impression&property=takeoverId&value=YOUR_TAKEOVER_ID for impressions/uniques and breakdowns?dimension=country&event=wall_impression&property=takeoverId&value=YOUR_TAKEOVER_ID for countries. Query the click event separately. Set from and to to an available time window covering the reign; the property filter keeps simultaneous/adjacent reigns separate. The API never hardcodes takeover semantics.

Limits and failures

60 requests/minute/key and 300/minute/workspace across API endpoints. Creating keys is limited to 10/hour/workspace. Cache aggregate results in your backend for at least 15 minutes for background reports, share them with your clients, and keep the last successful result if VisitorPing is temporarily unavailable. Do not launch a separate API poll for every public browser. Local immediate counters, if used, remain separate; never add them to VisitorPing counts.

Errors: { "error": { "code": "...", "message": "..." } }.

All API responses use Cache-Control: private, no-store and Vary: Authorization. Public embedding is a deliberate decision by the site owner through their own backend; the API itself never makes a site's analytics publicly accessible.

Track a conversion

After the VisitorPing tracker loads, record a conversion with a stable lead or order ID. Use whole cents and an uppercase three-letter currency. A missing conversionId is generated automatically; supplying the same ID on retries prevents duplicate conversions for that website. This is browser tracking, not a server-side payment confirmation API. Do not include buyer email or payment credentials.

window.VisitorPing?.conversion({
  conversionId: "order-123", // Reuse the same ID when retrying this conversion.
  name: "Purchase",
  valueCents: 1299,
  currency: "USD"
});

Outgoing alert webhooks

The existing notification webhooks remain available under Dashboard → Integrations. Each destination — custom webhook, Slack, Discord, or Telegram — belongs to one website; a website with nothing configured for a channel sends nothing on that channel. They send selected visitor alerts to an HTTPS destination. They are separate from this read-only aggregate API and are not a complete analytics event stream.

Configured custom alert webhooks send JSON with an event of visitor.arrival and the alert payload in data. Every delivery uses that one event name, including a qualified visitor — data.isHotLead and data.hotLeadReason carry that distinction. Delivery follows the configured notification rules; it is not an event for every page view. The current sender does not include an HMAC signature or an X-VisitorPing-Signature header. Do not use it as proof of payment or authorization for privileged actions.

Configure alert integrations