← Back to WWWHive

Server-side SDK setup

Store event API contract

Send signed commerce events from your store backend. WWWHive derives the tenant from the hashed API key, validates consent fields, enforces idempotency, and stores each event through a tenant-scoped repository.

Event endpoint

POST https://wwwhive.com/api/v1/events
Authorization
Bearer tenant server key
Idempotency-Key
Required; 8-191 safe characters
X-WWWHive-Timestamp
Required Unix time in seconds; five-minute window
X-WWWHive-Signature
HMAC-SHA256 of timestamp.rawBody, formatted as sha256=<digest>

Integration checklist

  1. 1Create a server API key in the merchant dashboard.
  2. 2Store the key in a server-only environment variable such as WWWHIVE_API_KEY.
  3. 3Serialize the JSON body before signing it.
  4. 4Sign timestamp.rawBody with HMAC-SHA256 and prefix the digest with sha256=.
  5. 5Send a stable Idempotency-Key so retries do not duplicate events.
  6. 6Confirm accepted events in /dashboard/events.

Accepted event names

product_viewcart_createdcart_updatedcheckout_startedpurchase_completedcustomer_created

Request and field mapping

The JSON body requires eventId, type, payload, and ISO-8601 occurredAt. Optional fields are customerId, cartId, value, and currency. A currency is required when value is present; customer creation must include payload.marketingConsent.

eventId
Merchant ID to external_event_id
customerId
Merchant ID to external_customer_id
occurredAt
Merchant time to occurred_at
tenantId
Never accepted; derived from the hashed API key

Copy-paste server examples

Node.js backend
import { createHmac, randomUUID } from "node:crypto";

const endpoint = "https://wwwhive.com/api/v1/events";
const apiKey = process.env.WWWHIVE_API_KEY;

if (!apiKey) {
  throw new Error("WWWHIVE_API_KEY must be set on the server.");
}

function signEvent(timestamp, body) {
  const digest = createHmac("sha256", apiKey)
    .update(`${timestamp}.${body}`, "utf8")
    .digest("hex");

  return `sha256=${digest}`;
}

export async function sendCheckoutStarted() {
  const eventId = `checkout-${randomUUID()}`;
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const body = JSON.stringify({
    eventId,
    type: "checkout_started",
    customerId: "customer-001",
    cartId: "cart-001",
    value: 129.99,
    currency: "USD",
    payload: { marketingConsent: "subscribed", source: "store-backend" },
    occurredAt: new Date().toISOString(),
  });

  const response = await fetch(endpoint, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
      "Idempotency-Key": eventId,
      "X-WWWHive-Timestamp": timestamp,
      "X-WWWHive-Signature": signEvent(timestamp, body),
    },
    body,
  });

  if (!response.ok) {
    throw new Error(await response.text());
  }

  return response.json();
}
Shopify Remix action
import { createHmac, randomUUID } from "node:crypto";
import { json } from "@remix-run/node";

const endpoint = "https://wwwhive.com/api/v1/events";

function signEvent(apiKey, timestamp, body) {
  const digest = createHmac("sha256", apiKey)
    .update(`${timestamp}.${body}`, "utf8")
    .digest("hex");

  return `sha256=${digest}`;
}

export async function action({ request }) {
  const apiKey = process.env.WWWHIVE_API_KEY;

  if (!apiKey) {
    throw new Response("WWWHIVE_API_KEY is not configured.", { status: 500 });
  }

  const formData = await request.formData();
  const cartId = String(formData.get("cartId") ?? randomUUID());
  const eventId = `shopify-checkout-${cartId}-${Date.now()}`;
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const body = JSON.stringify({
    eventId,
    type: "checkout_started",
    customerId: String(formData.get("customerId") ?? "guest"),
    cartId,
    value: Number(formData.get("value") ?? 0),
    currency: String(formData.get("currency") ?? "USD").toUpperCase(),
    payload: { marketingConsent: "unknown", source: "shopify-remix-action" },
    occurredAt: new Date().toISOString(),
  });

  const response = await fetch(endpoint, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
      "Idempotency-Key": eventId,
      "X-WWWHive-Timestamp": timestamp,
      "X-WWWHive-Signature": signEvent(apiKey, timestamp, body),
    },
    body,
  });

  if (!response.ok) {
    return json({ ok: false, error: await response.json() }, { status: 502 });
  }

  return json({ ok: true, event: await response.json() });
}
Windows PowerShell 5.1 smoke test
$timestamp = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds().ToString()
$eventId = "manual-test-" + [guid]::NewGuid().ToString("N")

$body = @{
  eventId = $eventId
  type = "checkout_started"
  customerId = "demo-customer-001"
  cartId = "demo-cart-001"
  value = 129.99
  currency = "USD"
  payload = @{ marketingConsent = "subscribed"; source = "manual-smoke" }
  occurredAt = [DateTimeOffset]::UtcNow.ToString("o")
} | ConvertTo-Json -Depth 10 -Compress

$hmac = [System.Security.Cryptography.HMACSHA256]::new(
  [Text.Encoding]::UTF8.GetBytes($env:WWWHIVE_API_KEY)
)
$signatureBytes = $hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes("$timestamp.$body"))
$signature = "sha256=" + [System.BitConverter]::ToString($signatureBytes).Replace("-", "").ToLowerInvariant()

$headers = @{
  Authorization = "Bearer $($env:WWWHIVE_API_KEY)"
  "Content-Type" = "application/json"
  "Idempotency-Key" = $eventId
  "X-WWWHive-Timestamp" = $timestamp
  "X-WWWHive-Signature" = $signature
}

Invoke-RestMethod -Uri "https://wwwhive.com/api/v1/events" -Method Post -Headers $headers -Body $body

Responses

A new event returns 202 with eventId, accepted, and receivedAt. An idempotent replay returns 200 and X-Idempotent-Replay: true. Errors return code, message, requestId, and optional fieldErrors. Expected error statuses are 400, 401, 413, 422, 429, and 503.

After a successful test, open /dashboard/events to confirm the accepted event is visible in the tenant-scoped event stream.