JustFlows

Shop · Integrate

Events and webhooks

Typed, versioned commerce events for plugins, delivered to other systems as signed webhooks.

Shop records every commerce change in a site-scoped outbox, in the same transaction as the change. A per-site job emits them after commit, and reconciliation catches missed jobs, so a listener failure can never undo a sale. Delivery is at least once.

Events

Every name starts with justflows.shop.. Every payload has version: 1, a stable eventKey, and occurredAt (ISO UTC).

EventFieldsWhen
order.createdorder, source; optional subscriptionId, periodCheckout, a renewal order, a staff assignment, or a default plan
order.paidorderThe payment is fully captured
order.payment_failedorder, nullable paymentIdAn online payment failed, was cancelled, or expired
order.cancelledorder, previousStatusStaff saved a cancellation
order.refundedorder, refundA local or external refund was recorded
order.shippedorder, shipmentSome or all lines shipped
order.deliveredorderTracking showed every shipment delivered
subscription.startedsubscription, changeStarted after payment, or without one
subscription.renewedsubscription, changeA renewal was paid
subscription.payment_failedsubscription, changeA subscription payment failed
subscription.updatedsubscription, changePaused, resumed, suspended, or a cancellation was scheduled or withdrawn
subscription.endedsubscription, changeCancelled, expired, or replaced
stock.lowstockA sale crossed the low-stock threshold or reached zero
  • order: id, number, status, placement time, currency, precision, totals, email and customer, addresses, shipping and payment method, locale, subscription id, and lines with product, variation, title, SKU, quantity, amounts, tax, and options.
  • subscription: id, number, status, customer, product and variation, quantity, renewal amount, interval, length, periods paid, period end, next payment, cancellation times, and the first order id. change has a type, a message, and a nullable order id.
  • refund: id, amount, status, reason, time, financial split, refunded lines and restock, and whether it was external. shipment: id, carrier and tracking, time, lines, and complete.
  • stock: inventory, product, content, and variation ids, SKU, available, reserved, and committed counts, the threshold, and level (low or out).

Listen from a plugin

typescript
import type { ShopEventMap } from "justflows.shop/contracts";

ctx.hooks.action("justflows.shop.order.paid", async (event) => {
  // event.order.totalMinor and event.order.lines are typed by Shop's contracts.
  await consumeOnce(event.eventKey, event.order);
});

Send to another system

Add an HTTPS endpoint under core Admin → Webhooks and select the Shop events. Core queues each delivery durably, retries with exponential backoff for up to an hour (five attempts), and keeps a delivery log where you can retry failures. The body is { id, event, createdAt, data } with the Shop payload in data.

HeaderValue
x-justflows-timestampUnix seconds.
x-justflows-signaturesha256=<hex>: HMAC-SHA256 of timestamp + "." + raw body with the endpoint's secret.
Verify a delivery (Node.js)
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyShopWebhook(rawBody, headers, secret) {
  const timestamp = headers["x-justflows-timestamp"];
  const signature = headers["x-justflows-signature"] ?? "";
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

  const expected = "sha256=" + createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
  return signature.length === expected.length && timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

// After verifying: deduplicate on data.eventKey, store the event, then answer 2xx.

Products are ordinary content, so core content webhooks already cover product changes: act on events where data.type is product.