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
| Method | Path | Body | Success |
|---|---|---|---|
| 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
| Status | Meaning | What to do |
|---|---|---|
| 201 / 202 | Accepted | Nothing |
| 400 | Invalid body (missing event_name, custom_id or events) | Fix the payload |
| 401 | Unknown or rotated project key | Use the key from Settings → Project |
| 413 | Body too large | Split the batch |
| 429 | Rate limited, or monthly quota exceeded after the grace period ({"error":"quota_exceeded"}) | Back off; upgrade the plan |
| 5xx | Server error | Retry 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.