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/eventsAuthentication
Server-side requests are authenticated using HMAC-SHA256 signatures. Every request must include two headers:
| Header | Value | Description |
|---|---|---|
X-Signature | sha256={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 theX-Timestampheader (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
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | track, page, identify, or group |
event | string | Track only | Event name |
userId | string | Recommended | Known user ID |
anonymousId | string | No | Anonymous ID (use if userId is not available) |
properties | object | No | Event properties |
traits | object | No | User/group traits (for identify/group events) |
context | object | No | Contextual data (IP, user agent, etc.) |
context.ip | string | Recommended | Client IP address for geolocation enrichment |
context.userAgent | string | Recommended | Client user agent for device parsing |
timestamp | string | No | ISO 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.ipandcontext.userAgentwhen you have them. This enables geolocation enrichment and device parsing in the processing layer. - Use
userIdwhenever possible. Server-side events are most valuable when tied to a known user identity. - Set
timestampto 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.