IngestionServer-Side Events

Server-Side Events

The server-side endpoint allows your backend services to submit events directly to Datafly Signal without going through the browser. This is the recommended approach for events that originate on the server, such as order completions, subscription changes, or backend-computed conversions.

Endpoint

POST /v1/events

Authentication

Server-side requests are authenticated using HMAC-SHA256 signatures. Every request must include two headers:

HeaderValueDescription
X-Signaturesha256={hmac_hex}HMAC-SHA256 signature of the request
X-Timestamp{unix_ms}Current time in Unix milliseconds

HMAC Computation

The signature is computed as:

HMAC-SHA256(secret_key, timestamp + "." + request_body)

Where:

  • secret_key — your source’s secret key (available in the Management UI)
  • timestamp — the value of the X-Timestamp header (Unix milliseconds as a string)
  • request_body — the raw JSON request body
⚠️

The gateway rejects requests where the timestamp is more than 5 minutes from the server’s current time. Make sure your server’s clock is synchronised via NTP.

Request Format

{
  "type": "track",
  "event": "Order Completed",
  "userId": "user_98765",
  "properties": {
    "order_id": "ORD-2026-1234",
    "revenue": 149.99,
    "currency": "USD",
    "products": [
      {
        "product_id": "SKU-1234",
        "name": "Wireless Headphones",
        "price": 79.99,
        "quantity": 1
      },
      {
        "product_id": "SKU-5678",
        "name": "USB-C Cable",
        "price": 14.99,
        "quantity": 2
      }
    ]
  },
  "context": {
    "ip": "203.0.113.42",
    "userAgent": "Mozilla/5.0 ..."
  },
  "timestamp": "2026-02-25T14:30:00.000Z"
}

Fields

FieldTypeRequiredDescription
typestringYestrack, page, identify, or group
eventstringTrack onlyEvent name
userIdstringRecommendedKnown user ID
anonymousIdstringNoAnonymous ID (use if userId is not available)
propertiesobjectNoEvent properties
traitsobjectNoUser/group traits (for identify/group events)
contextobjectNoContextual data (IP, user agent, etc.)
context.ipstringRecommendedClient IP address for geolocation enrichment
context.userAgentstringRecommendedClient user agent for device parsing
timestampstringNoISO 8601 timestamp; defaults to server receipt time

You should provide at least one of userId or anonymousId. If both are present, the Identity Hub will link them together.

Response

{
  "success": true
}

Status codes: 200 on success, 400 for invalid payload, 401 for invalid signature, 403 for expired timestamp, 429 if rate-limited.

Examples

curl

SECRET_KEY="sk_live_your_secret_key"
TIMESTAMP=$(date +%s000)
BODY='{"type":"track","event":"Order Completed","userId":"user_98765","properties":{"order_id":"ORD-2026-1234","revenue":149.99,"currency":"USD"}}'
 
SIGNATURE=$(printf '%s.%s' "$TIMESTAMP" "$BODY" | openssl dgst -sha256 -hmac "$SECRET_KEY" | awk '{print $2}')
 
curl -X POST https://data.example.com/v1/events \
  -H "Content-Type: application/json" \
  -H "X-Signature: sha256=$SIGNATURE" \
  -H "X-Timestamp: $TIMESTAMP" \
  -d "$BODY"

Node.js

import crypto from "node:crypto";
 
const SECRET_KEY = "sk_live_your_secret_key";
const ENDPOINT = "https://data.example.com/v1/events";
 
async function sendEvent(event) {
  const timestamp = Date.now().toString();
  const body = JSON.stringify(event);
  const signature = crypto
    .createHmac("sha256", SECRET_KEY)
    .update(`${timestamp}.${body}`)
    .digest("hex");
 
  const response = await fetch(ENDPOINT, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Signature": `sha256=${signature}`,
      "X-Timestamp": timestamp,
    },
    body,
  });
 
  return response.json();
}
 
await sendEvent({
  type: "track",
  event: "Order Completed",
  userId: "user_98765",
  properties: {
    order_id: "ORD-2026-1234",
    revenue: 149.99,
    currency: "USD",
  },
});

Python

import hashlib
import hmac
import json
import time
import requests
 
SECRET_KEY = "sk_live_your_secret_key"
ENDPOINT = "https://data.example.com/v1/events"
 
 
def send_event(event: dict) -> dict:
    timestamp = str(int(time.time() * 1000))
    body = json.dumps(event, separators=(",", ":"))
    signature = hmac.new(
        SECRET_KEY.encode(),
        f"{timestamp}.{body}".encode(),
        hashlib.sha256,
    ).hexdigest()
 
    response = requests.post(
        ENDPOINT,
        headers={
            "Content-Type": "application/json",
            "X-Signature": f"sha256={signature}",
            "X-Timestamp": timestamp,
        },
        data=body,
    )
    return response.json()
 
 
send_event({
    "type": "track",
    "event": "Order Completed",
    "userId": "user_98765",
    "properties": {
        "order_id": "ORD-2026-1234",
        "revenue": 149.99,
        "currency": "USD",
    },
})

Go

package main
 
import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"time"
)
 
const (
	secretKey = "sk_live_your_secret_key"
	endpoint  = "https://data.example.com/v1/events"
)
 
func sendEvent(event map[string]any) error {
	body, err := json.Marshal(event)
	if err != nil {
		return err
	}
 
	timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10)
	mac := hmac.New(sha256.New, []byte(secretKey))
	mac.Write([]byte(timestamp + "." + string(body)))
	signature := hex.EncodeToString(mac.Sum(nil))
 
	req, err := http.NewRequest("POST", endpoint, bytes.NewReader(body))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-Signature", "sha256="+signature)
	req.Header.Set("X-Timestamp", timestamp)
 
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
 
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("unexpected status: %d", resp.StatusCode)
	}
	return nil
}
 
func main() {
	event := map[string]any{
		"type":   "track",
		"event":  "Order Completed",
		"userId": "user_98765",
		"properties": map[string]any{
			"order_id": "ORD-2026-1234",
			"revenue":  149.99,
			"currency": "USD",
		},
	}
 
	if err := sendEvent(event); err != nil {
		fmt.Println("Error:", err)
	}
}

Best Practices

  • Always include context.ip and context.userAgent when you have them. This enables geolocation enrichment and device parsing in the processing layer.
  • Use userId whenever possible. Server-side events are most valuable when tied to a known user identity.
  • Set timestamp to the actual event time, not the time your server processes it. This ensures accurate attribution windows in downstream vendors.
  • Rotate secret keys periodically via the Management API. The gateway supports key rotation with a grace period for the previous key.