POST /api/track ingests events from Salla themes (Angel + Neptune), verifies HMAC, and stores them in tracking_events for analytics.

#Request

http
POST /api/track HTTP/1.1
Host: afterads.com
Content-Type: application/json
X-Signature: 5e88e2d4...
X-Timestamp: 1715174400

{
  "event": "product_view",
  "store_id": "salla:1234567890",
  "session_id": "...",
  "user_id": "salla:9876543210",
  "metadata": {
    "product_id": "1828908128",
    "category": "fashion",
    "currency": "SAR",
    "price": 350
  }
}

#Event types

EventDescriptionmetadata required
store_viewGeneric visitreferrer, page
product_viewProduct page viewproduct_id, category
addtocartAdd to cartproduct_id, quantity, price
begin_checkoutCheckout startcarttotal, itemscount
purchaseOrder completeorder_id, total, items
signupCustomer signupemail_hash
review_submitProduct reviewproduct_id, rating
searchSite searchquery, results_count

#HMAC verification

The server verifies X-Signature like this:

typescript
import { createHmac } from 'crypto';

const expected = createHmac('sha256', SECRET_KEY)
  .update(timestamp + ':' + JSON.stringify(body))
  .digest('hex');

if (!constantTimeEqual(expected, providedSignature)) {
  return 401;
}

#Signing key

#Full example (Salla theme)

javascript
// Inside a Salla theme (server- or client-side)
async function trackEvent(event, metadata) {
  const ts = Math.floor(Date.now() / 1000);
  const body = JSON.stringify({
    event,
    store_id: salla.config.store.id,
    session_id: getSessionId(),
    user_id: salla.user?.id,
    metadata,
  });
  const signature = await sign(ts + ':' + body, AFTERADS_TRACKING_KEY);

  await fetch('https://afterads.com/api/track', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Signature': signature,
      'X-Timestamp': String(ts),
    },
    body,
  });
}

trackEvent('add_to_cart', {
  product_id: '1828908128',
  quantity: 1,
  price: 350,
});

#Response

"Code""Body"
200{ "ok": true, "event_id": "uuid" }
401{ "error": "invalid_signature" }
401{ "error": "stale_timestamp" }
409{ "error": "replay" } (nonce reused)
400{ "error": "invalideventtype" }
429{ "error": "ratelimited", "retryafter_seconds": 60 }

#Anti-replay

Each request has a unique session_id + timestamp. The server stores them for 5 minutes and rejects duplicates — preventing replay attacks if someone sniffs the network.