PixelMili

Signed Server API

Send purchases and order-status changes from SellSull, WooCommerce, Shopify or any custom backend. PixelMili verifies every request, stores it durably, then processes it on reserved commerce queues. Browser events continue through the normal tracking script.

1. Create credentials

  1. Dashboard → Domains → Install → Server API.
  2. Generate a server secret and copy it immediately; PixelMili only shows it once.
  3. Save the Tracking ID and secret in your platform's encrypted server settings. Never expose the secret in HTML or JavaScript.

Keep Meta/TikTok pixel IDs and access tokens in PixelMili's Integrations page. A SellSull vendor only enters the PixelMili Tracking ID and server secret in SellSull—platform access tokens are never copied there.

2. Endpoint and signature

http
POST https://track.pixelmili.com/v1/connectors/events
Content-Type: application/json
X-PixelMili-Timestamp: CURRENT_UNIX_TIMESTAMP
X-PixelMili-Signature: sha256=HEX_HMAC

The signature is HMAC-SHA256 over these exact bytes:

text
{unix_timestamp}\n{connector}\n{raw_json_request_body}

Valid connectors are sellsull, woocommerce, shopify and custom. The timestamp must be within five minutes of PixelMili's clock. Sign the exact JSON string you send—do not encode it a second time.

3. Event contract

FieldWhat to send
tracking_id requiredThe domain Tracking ID, for example pm_abc123xyz.
connector requiredThe same connector name included in the signature.
event_name requiredPurchase, OrderConfirmed, OrderDelivered, OrderCancelled, OrderReturned, OrderFailed or Refund. A store whose server also sees the shopper's cart actions may send AddToCart and InitiateCheckout too — use the same event_id as the tracker script's own event (pm('track', 'AddToCart', data, identity, { event_id: '…' })): PixelMili forwards whichever of the two arrives first and drops the other, so a blocked script loses nothing and an unblocked one is not counted twice.
event_id requiredA stable, unique ID for this event, e.g. sellsull:purchase:1001. Every retry must reuse it.
event_timeUnix seconds or milliseconds. Defaults to the receipt time.
client_ip, user_agentThe buyer's checkout IP and browser—not your server's—for better ad matching.
client_id, fbp, fbc, gaVisitor identifiers captured by the browser script.
identityCustomer phone, email, first_name, last_name, city, state, zip, country. PixelMili normalises and hashes these for each destination.
customorder_id, value, currency, contents, content_ids, num_items, cod and captured click IDs.
purchase.json
{
  "tracking_id": "pm_YOUR_TRACKING_ID",
  "connector": "sellsull",
  "event_name": "Purchase",
  "event_id": "sellsull:purchase:1001",
  "event_time": 1792814400,
  "client_ip": "203.0.113.10",
  "user_agent": "Mozilla/5.0 ...",
  "client_id": "visitor-id-from-pm_cid",
  "fbp": "fb.1.1700000000.123456789",
  "identity": {
    "phone": "01712345678",
    "email": "buyer@example.com",
    "first_name": "Rahim",
    "city": "Dhaka",
    "country": "BD"
  },
  "custom": {
    "order_id": "1001",
    "value": 1490,
    "currency": "BDT",
    "cod": true,
    "contents": [{"id": "SKU-1", "quantity": 1, "item_price": 1490}]
  }
}

4. Delivery and retry rules

  • 202 with accepted: true means PixelMili durably owns the event. Stop retrying.
  • queued: false on that accepted response is still success: the event is safe in PixelMili's SQL inbox and will be queued automatically when worker capacity returns.
  • duplicate: true means the same event_id was already accepted; this is a successful idempotent retry.
  • 409 means that event_id was reused with different JSON. Stop retrying and fix the sender's event-ID generation.
  • Retry 429 and 5xx with exponential backoff and jitter, always using the same event_id.
  • Do not retry 401 or 422 forever; fix the secret/signature or payload and alert the merchant.
  • Keep failed events—and the exact serialized JSON body—in your own durable queue/outbox until PixelMili returns an accepted 202.

PHP example

php
$payload = [
    'tracking_id' => getenv('PIXELMILI_TRACKING_ID'),
    'connector'   => 'sellsull',
    'event_name'  => 'Purchase',
    'event_id'    => 'sellsull:purchase:' . $order->id,
    'client_ip'   => $checkoutIp,
    'user_agent'  => $checkoutUserAgent,
    'client_id'   => $visitorCookies['pm_cid'] ?? null,
    'fbp'         => $visitorCookies['_fbp'] ?? null,
    'fbc'         => $visitorCookies['_fbc'] ?? null,
    'identity'    => ['phone' => $order->phone, 'email' => $order->email, 'country' => 'BD'],
    'custom'      => ['order_id' => (string) $order->id, 'value' => (float) $order->total, 'currency' => 'BDT', 'cod' => $order->is_cod],
];

$body = json_encode($payload, JSON_UNESCAPED_SLASHES);
$timestamp = (string) time();
$signature = hash_hmac('sha256', $timestamp . "\n" . $payload['connector'] . "\n" . $body, getenv('PIXELMILI_SERVER_SECRET'));

$ch = curl_init('https://track.pixelmili.com/v1/connectors/events');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $body,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'X-PixelMili-Timestamp: ' . $timestamp,
        'X-PixelMili-Signature: sha256=' . $signature,
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 8,
]);
$responseBody = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

// Mark the local outbox row delivered only when $status === 202 and accepted === true.

Node.js example

js
import crypto from 'node:crypto';

async function sendPixelMili(payload) {
  const body = JSON.stringify(payload);
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const canonical = `${timestamp}\n${payload.connector}\n${body}`;
  const signature = crypto.createHmac('sha256', process.env.PIXELMILI_SERVER_SECRET)
    .update(canonical).digest('hex');

  const response = await fetch('https://track.pixelmili.com/v1/connectors/events', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-PixelMili-Timestamp': timestamp,
      'X-PixelMili-Signature': `sha256=${signature}`,
    },
    body,
    signal: AbortSignal.timeout(8000),
  });

  const result = await response.json().catch(() => ({}));
  if (response.status !== 202 || result.accepted !== true) {
    throw new Error(`PixelMili did not accept event (${response.status})`);
  }
  return result;
}

Purchase ownership and COD

Exactly one system should own the initial Purchase. If your platform sends it server-side, add data-server-events="Purchase" to the PixelMili script so the thank-you page does not send a second browser Purchase. Send order-status events with the same custom.order_id, but give each status event its own stable event_id.

html
<script async src="https://track.pixelmili.com/t.js"
        data-tracking-id="pm_YOUR_TRACKING_ID"
        data-server-events="Purchase"></script>

PixelMili's COD setting decides whether a COD Purchase is sent when placed, held until confirmation/delivery, or sent at both stages. The commerce platform should report facts—placed, confirmed, delivered, cancelled or returned—and must not independently invent a second Purchase.

Go-live checklist

  • Secret stored only in encrypted server-side configuration.
  • Stable event IDs persisted before the first send.
  • Local durable retries for timeouts, 429 and 5xx.
  • Buyer cookies, IP and user-agent captured at checkout and reused for later COD release.
  • Same custom.order_id across the purchase lifecycle.
  • Clock synchronised with NTP so signed requests stay inside the five-minute window.
ESC

Full-text search is coming soon. Use the sidebar for now.