Skip to content
IForevents Docs
IForevents Docs
HomeDashboardGitHubPrivacyTermsSecurityIntroduction
Support and SLA

HTTP Ingest (Server-Side)

Send events from your backend with plain HTTP

Any language with an HTTP client can send events. Authenticate every request with the project key in the X-Project-Key header, name the person in X-User-Id, and send JSON.

Endpoints

MethodPathBodySuccess
POST/v1/events/track{"event_name", "event_type"?, "properties"?}201
POST/v1/events/batch{"events": [{"name", "type"?, "properties"?, "created_at"?}, ...]} (max 500)202
POST/v1/events/identify{"custom_id", "name"?, "email"?, "phone_number"?, "properties"?}201 with the user

X-User-Id is your id for the person (the same value identify takes as custom_id), any string up to 256 characters. Always send it from a server: every request from your backend arrives from the same address, and without the header the api would file all of them under one address-derived profile (why). For visitors you have not signed up yet, generate an id once per visitor (a cookie or session id works) and keep sending it.

curl

export IFOREVENTS_PROJECT_KEY="<project key>"

# track
curl -X POST https://api.iforevents.com/v1/events/track \
  -H "X-Project-Key: $IFOREVENTS_PROJECT_KEY" \
  -H "X-User-Id: user-123" \
  -H "Content-Type: application/json" \
  -d '{"event_name":"purchase","properties":{"amount":"99.99","currency":"USD"}}'

# identify (optional: profile details for the same id)
curl -X POST https://api.iforevents.com/v1/events/identify \
  -H "X-Project-Key: $IFOREVENTS_PROJECT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"custom_id":"user-123","email":"[email protected]","properties":{"plan":"pro"}}'

Node.js

const key = process.env.IFOREVENTS_PROJECT_KEY;

async function track(userId, eventName, properties) {
  const res = await fetch("https://api.iforevents.com/v1/events/track", {
    method: "POST",
    headers: {
      "X-Project-Key": key,
      "X-User-Id": userId,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ event_name: eventName, properties }),
  });
  if (!res.ok) throw new Error(`iforevents ${res.status}: ${await res.text()}`);
}

// In a route handler: the signed-in user's id, or the visitor's session id.
await track(req.user?.id ?? req.session.id, "purchase", { amount: "99.99", currency: "USD" });

Python

import os
import requests

KEY = os.environ["IFOREVENTS_PROJECT_KEY"]
BASE = "https://api.iforevents.com"


def track(user_id: str, event_name: str, properties: dict) -> None:
    headers = {"X-Project-Key": KEY, "X-User-Id": user_id}
    res = requests.post(f"{BASE}/v1/events/track", json={"event_name": event_name, "properties": properties}, headers=headers, timeout=10)
    res.raise_for_status()


track("user-123", "purchase", {"amount": "99.99", "currency": "USD"})

Go

body, _ := json.Marshal(map[string]any{"event_name": "purchase", "properties": map[string]string{"amount": "99.99"}})
req, _ := http.NewRequest(http.MethodPost, "https://api.iforevents.com/v1/events/track", bytes.NewReader(body))
req.Header.Set("X-Project-Key", os.Getenv("IFOREVENTS_PROJECT_KEY"))
req.Header.Set("X-User-Id", userID) // your id for the person
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)

Batching

Buffer events on your side and flush every few seconds or every 500 events:

{
  "events": [
    { "name": "page_view", "type": "page_view", "properties": { "page": "/" } },
    { "name": "button_click", "properties": { "button": "signup" }, "created_at": "2026-09-11T12:00:00Z" }
  ]
}

One batch is one user (the X-User-Id of the request), so group buffered events by user before flushing. Batches are accepted with 202 and processed asynchronously; keep them under 500 items and a few hundred kilobytes.

Errors

StatusMeaningWhat to do
201 / 202AcceptedNothing
400Invalid body (missing event_name, custom_id or events)Fix the payload
401Unknown or rotated project keyUse the key from Settings → Project
413Body too largeSplit the batch
429Rate limited, or monthly quota exceeded after the grace period ({"error":"quota_exceeded"})Back off; upgrade the plan
5xxServer errorRetry with exponential backoff

Retry only when no 2xx came back (network errors, 5xx, 429): an accepted event gets its own id on the server, so re-sending an accepted batch counts it twice.

On this page

EndpointscurlNode.jsPythonGoBatchingErrors