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