If you’re using a framework not covered by our SDKs, or need full control over tracking, you can integrate directly with the Sparklytics HTTP API.
API Endpoint
All events are sent to:
POST https://your-sparklytics-server.com/api/collect
Authentication
No authentication required for event collection. Events are associated with websites via the website_id field.
Events with unknown website_id values are rejected with 404 Not Found.
Event Schema
Every event must include:
{
"website_id" : "YOUR_WEBSITE_ID" ,
"type" : "pageview" | "event" ,
"url" : "https://example.com/page"
}
Pageview Event
{
"website_id" : "abc123" ,
"type" : "pageview" ,
"url" : "https://example.com/pricing" ,
"referrer" : "https://google.com/search?q=analytics" ,
"language" : "en-US" ,
"screen" : "1920x1080" ,
"screen_width" : 1920 ,
"screen_height" : 1080 ,
"utm_source" : "google" ,
"utm_medium" : "cpc" ,
"utm_campaign" : "spring-sale"
}
Custom Event
{
"website_id" : "abc123" ,
"type" : "event" ,
"url" : "https://example.com/pricing" ,
"event_name" : "signup_click" ,
"event_data" : {
"plan" : "pro" ,
"source" : "hero_cta"
},
"referrer" : "https://example.com/home" ,
"language" : "en-US"
}
Field Reference
Field Type Required Max Length Description website_idstringYes 64 chars Your website ID from dashboard type"pageview" | "event"Yes - Event type urlstringYes 2048 chars Current page URL event_namestringFor type=event 50 chars Custom event name event_dataobjectNo 4 KB serialized Custom event properties referrerstringNo 2048 chars HTTP Referer header visitor_idstringNo 64 chars Stable visitor identifier languagestringNo 10 chars Browser language (e.g., en-US) screenstringNo 20 chars Screen resolution (e.g., 1920x1080) screen_widthnumberNo - Screen width in pixels screen_heightnumberNo - Screen height in pixels utm_sourcestringNo 100 chars UTM source parameter utm_mediumstringNo 100 chars UTM medium parameter utm_campaignstringNo 100 chars UTM campaign parameter utm_termstringNo 100 chars UTM term parameter utm_contentstringNo 100 chars UTM content parameter
Automatic Enrichment
The server automatically enriches events with:
GeoIP data : country, region, city (from client IP)
User agent parsing : browser, browser_version, os, os_version, device_type
Referrer domain : Extracted from referrer URL
Visitor ID : If not provided, computed as sha256(salt_epoch + ip + user_agent)[0:16]
Session ID : Automatically assigned (30-minute inactivity timeout)
You don’t need to parse user agents or do GeoIP lookups yourself — the server handles this automatically.
Batch API
Send up to 50 events in a single request:
[
{
"website_id" : "abc123" ,
"type" : "pageview" ,
"url" : "https://example.com/home"
},
{
"website_id" : "abc123" ,
"type" : "event" ,
"url" : "https://example.com/home" ,
"event_name" : "cta_click" ,
"event_data" : { "button" : "hero" }
}
]
Batches larger than 50 events return 400 Bad Request with error code batch_too_large.
Response
Success
HTTP/ 1.1 202 Accepted
Content-Type: application/json
x-sparklytics-ingest-ack: queued
{
"ok" : true
}
Error: Unknown Website
HTTP/ 1.1 404 Not Found
Content-Type: application/json
{
"error" : "Unknown website_id: abc123"
}
Error: Rate Limited
HTTP/ 1.1 429 Too Many Requests
Content-Type: application/json
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1678901234
{
"error" : "Rate limit exceeded"
}
Rate limit: 60 requests per minute per IP address . The limit applies to the /api/collect endpoint only.
Example Implementations
JavaScript (Fetch)
Python
Go
Ruby
const SPARKLYTICS_HOST = 'https://analytics.example.com'
const WEBSITE_ID = 'abc123'
async function trackPageview ( url ) {
const payload = {
website_id: WEBSITE_ID ,
type: 'pageview' ,
url: url || window . location . href ,
referrer: document . referrer || undefined ,
language: navigator . language ,
screen: ` ${ screen . width } x ${ screen . height } `
}
await fetch ( ` ${ SPARKLYTICS_HOST } /api/collect` , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ( payload ),
keepalive: true
})
}
async function trackEvent ( eventName , eventData ) {
const payload = {
website_id: WEBSITE_ID ,
type: 'event' ,
url: window . location . href ,
event_name: eventName ,
event_data: eventData ,
language: navigator . language
}
await fetch ( ` ${ SPARKLYTICS_HOST } /api/collect` , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ( payload ),
keepalive: true
})
}
// Track initial pageview
trackPageview ()
// Track custom event
document . querySelector ( '#signup-btn' ). addEventListener ( 'click' , () => {
trackEvent ( 'signup_click' , { plan: 'pro' })
})
import requests
from typing import Optional, Dict, Any
SPARKLYTICS_HOST = 'https://analytics.example.com'
WEBSITE_ID = 'abc123'
def track_pageview (
url : str ,
referrer : Optional[ str ] = None ,
visitor_id : Optional[ str ] = None
):
payload = {
'website_id' : WEBSITE_ID ,
'type' : 'pageview' ,
'url' : url
}
if referrer:
payload[ 'referrer' ] = referrer
if visitor_id:
payload[ 'visitor_id' ] = visitor_id
response = requests.post(
f ' { SPARKLYTICS_HOST } /api/collect' ,
json = payload,
timeout = 5
)
response.raise_for_status()
def track_event (
event_name : str ,
url : str ,
event_data : Optional[Dict[ str , Any]] = None ,
visitor_id : Optional[ str ] = None
):
payload = {
'website_id' : WEBSITE_ID ,
'type' : 'event' ,
'url' : url,
'event_name' : event_name
}
if event_data:
payload[ 'event_data' ] = event_data
if visitor_id:
payload[ 'visitor_id' ] = visitor_id
response = requests.post(
f ' { SPARKLYTICS_HOST } /api/collect' ,
json = payload,
timeout = 5
)
response.raise_for_status()
# Example usage
track_pageview( 'https://example.com/pricing' )
track_event( 'signup_click' , 'https://example.com/pricing' , {
'plan' : 'pro' ,
'source' : 'hero_cta'
})
package main
import (
" bytes "
" encoding/json "
" net/http "
)
const (
SparklyticsHost = "https://analytics.example.com"
WebsiteID = "abc123"
)
type Event struct {
WebsiteID string `json:"website_id"`
Type string `json:"type"`
URL string `json:"url"`
EventName string `json:"event_name,omitempty"`
EventData map [ string ] interface {} `json:"event_data,omitempty"`
Referrer string `json:"referrer,omitempty"`
VisitorID string `json:"visitor_id,omitempty"`
}
func TrackPageview ( url , referrer string ) error {
event := Event {
WebsiteID : WebsiteID ,
Type : "pageview" ,
URL : url ,
Referrer : referrer ,
}
return sendEvent ( event )
}
func TrackEvent ( eventName , url string , eventData map [ string ] interface {}) error {
event := Event {
WebsiteID : WebsiteID ,
Type : "event" ,
URL : url ,
EventName : eventName ,
EventData : eventData ,
}
return sendEvent ( event )
}
func sendEvent ( event Event ) error {
payload , err := json . Marshal ( event )
if err != nil {
return err
}
resp , err := http . Post (
SparklyticsHost + "/api/collect" ,
"application/json" ,
bytes . NewBuffer ( payload ),
)
if err != nil {
return err
}
defer resp . Body . Close ()
if resp . StatusCode != http . StatusAccepted {
return fmt . Errorf ( "unexpected status: %d " , resp . StatusCode )
}
return nil
}
func main () {
TrackPageview ( "https://example.com/pricing" , "https://google.com" )
TrackEvent ( "signup_click" , "https://example.com/pricing" , map [ string ] interface {}{
"plan" : "pro" ,
"source" : "hero_cta" ,
})
}
require 'net/http'
require 'json'
SPARKLYTICS_HOST = 'https://analytics.example.com'
WEBSITE_ID = 'abc123'
def track_pageview ( url , referrer: nil , visitor_id: nil )
payload = {
website_id: WEBSITE_ID ,
type: 'pageview' ,
url: url
}
payload[ :referrer ] = referrer if referrer
payload[ :visitor_id ] = visitor_id if visitor_id
send_event (payload)
end
def track_event ( event_name , url , event_data: nil , visitor_id: nil )
payload = {
website_id: WEBSITE_ID ,
type: 'event' ,
url: url,
event_name: event_name
}
payload[ :event_data ] = event_data if event_data
payload[ :visitor_id ] = visitor_id if visitor_id
send_event (payload)
end
def send_event ( payload )
uri = URI ( " #{ SPARKLYTICS_HOST } /api/collect" )
http = Net :: HTTP . new (uri. host , uri. port )
http. use_ssl = true
request = Net :: HTTP :: Post . new (uri. path , 'Content-Type' => 'application/json' )
request. body = payload. to_json
response = http. request (request)
raise "HTTP #{ response. code } " unless response. code . to_i == 202
end
# Example usage
track_pageview ( 'https://example.com/pricing' )
track_event ( 'signup_click' , 'https://example.com/pricing' , event_data: {
plan: 'pro' ,
source: 'hero_cta'
})
Server-Side Tracking
When tracking from a server (e.g., API endpoints, webhooks), you should:
Set a stable visitor_id (e.g., user ID or session ID)
Pass the original url (the page the user was on)
Avoid tracking internal requests (only track user-facing actions)
Example: Track Server-Side Conversions
import requests
def track_conversion ( user_id : str , plan : str , price : float ):
"""Track a successful subscription from Stripe webhook"""
requests.post(
'https://analytics.example.com/api/collect' ,
json = {
'website_id' : 'abc123' ,
'type' : 'event' ,
'url' : 'https://example.com/checkout' , # Original page
'event_name' : 'subscription_created' ,
'event_data' : {
'plan' : plan,
'price' : price,
'currency' : 'USD'
},
'visitor_id' : user_id # Stable user ID
},
timeout = 5
)
CORS Configuration
If you’re tracking from a different domain than your Sparklytics server, configure CORS:
# Add to your .env or docker-compose.yml
SPARKLYTICS_CORS_ORIGINS = https://example.com,https://www.example.com
The /api/collect endpoint is designed for client-side tracking. Use SPARKLYTICS_CORS_ORIGINS to allow cross-origin requests from your frontend domains.
Rate Limiting
Limit : 60 requests per minute per IP
Scope : Applies to /api/collect only
Headers : X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
Handling Rate Limits
async function trackWithRetry ( payload , maxRetries = 3 ) {
for ( let attempt = 0 ; attempt < maxRetries ; attempt ++ ) {
const response = await fetch ( 'https://analytics.example.com/api/collect' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ( payload )
})
if ( response . status === 202 ) {
return // Success
}
if ( response . status === 429 ) {
const resetTime = parseInt ( response . headers . get ( 'X-RateLimit-Reset' ))
const waitMs = ( resetTime * 1000 ) - Date . now ()
await new Promise ( resolve => setTimeout ( resolve , waitMs ))
continue
}
throw new Error ( `HTTP ${ response . status } ` )
}
}
Best Practices
Use keepalive for pageview tracking
When tracking pageviews during navigation, use keepalive: true to ensure the request completes even if the page unloads: fetch ( '/api/collect' , {
method: 'POST' ,
body: JSON . stringify ( payload ),
keepalive: true // Important!
})
Deduplicate rapid-fire events
Prevent duplicate pageviews when using client-side routers: let lastUrl = ''
let lastTimestamp = 0
function trackPageview ( url ) {
const now = Date . now ()
if ( url === lastUrl && now - lastTimestamp < 100 ) {
return // Skip duplicate
}
lastUrl = url
lastTimestamp = now
// ... send event
}
Batch events when possible
Reduce network overhead by batching multiple events: const eventQueue = []
function queueEvent ( event ) {
eventQueue . push ( event )
if ( eventQueue . length >= 10 ) {
flushEvents ()
}
}
function flushEvents () {
if ( eventQueue . length === 0 ) return
fetch ( '/api/collect' , {
method: 'POST' ,
body: JSON . stringify ( eventQueue ),
keepalive: true
})
eventQueue . length = 0
}
// Flush on page unload
window . addEventListener ( 'beforeunload' , flushEvents )
Honor Do Not Track and Global Privacy Control: function shouldTrack () {
if ( navigator . doNotTrack === '1' ) return false
if ( navigator . globalPrivacyControl === true ) return false
return true
}
Testing Your Integration
Use curl to verify your setup:
curl -X POST https://analytics.example.com/api/collect \
-H "Content-Type: application/json" \
-d '{
"website_id": "abc123",
"type": "pageview",
"url": "https://example.com/test"
}'
Expected response:
Next Steps
HTML Snippet Use the pre-built snippet for standard websites
API Reference Full API documentation for all endpoints