API guide

Storefront integration

Storefront integration

Sell personalized print from any storefront — Odoo, WooCommerce, Shopify, or a bespoke cart — with the same five touchpoints. StackFill stays platform-neutral: your product, variant, cart, and order references are opaque strings it never interprets, the widget speaks postMessage, and every server call is a Bearer-authed /v1 request. An integration is glue, not business logic — if you find yourself encoding print rules in the storefront, that logic belongs in StackFill instead.

The contract at a glance

# Touchpoint Direction Mechanism
1 Personalize browser → StackFill Load widget/v1.js; open with a publishable key + your product/variant ids.
2 Attach to cart browser → your platform Catch stackfill:saved; store fill.id on the cart line.
3 Finalize on order your server → StackFill POST /v1/fills/{id}/finalize with your external_order_id.
4 Fulfill your server → StackFill GET /v1/renders?external_order_id=… → signed production-PDF URL.
5 Listing imagery your platform ← StackFill POST /v1/previews for product/listing thumbnails.

That is the whole surface. A WooCommerce plugin, a Shopify app, and the Odoo stackfill_connector module all implement exactly these five steps — nothing in StackFill core is platform-specific.

1 · Personalize (browser)

Embed the widget on the product page. The publishable key is origin-allowlisted and safe to ship to the browser; the product and variant ids are whatever your platform calls them — StackFill resolves them through product mappings.

<script src="https://stackfill.com/widget/v1.js" async></script>
<button
  data-stackfill-key="pk_live_…"
  data-stackfill-product="SKU-OR-PRODUCT-ID"
  data-stackfill-variant="VARIANT-ID"
  data-stackfill-cart="CART-OR-SESSION-ID">
  Personalize
</button>

2 · Attach to cart (browser)

When the shopper saves, the widget emits a stackfill:saved message whose payload carries both the embed_session and the fill. Take fill.id and write it onto the cart line however your platform stores line metadata — a hidden input, a cart attribute, a line-item property.

window.addEventListener('message', (e) => {
  if (e.data?.type !== 'stackfill:saved') return;
  const fillId = e.data.fill.id;
  // e.g. Odoo: POST /shop/cart/update (form-encoded, with csrf_token),
  // writing fillId into the order-line attributes.
  attachToCartLine(fillId, e.data.fill.preview_url);
});

See Widget events for the full payload.

3 · Finalize on order (server)

On order confirmation, finalize the fill from your server with your order reference. This is the platform-neutral fulfillment primitive — StackFill records external_order_id and returns the production render id.

curl https://api.stackfill.com/v1/fills/FILL_ID/finalize \
  -H "Authorization: Bearer stackfill_live_sk_…" \
  -H "Content-Type: application/json" \
  -d '{ "external_order_id": "YOUR-ORDER-REF" }'

Store the returned render_id on the order.

4 · Fulfill (server)

Resolve the production PDF by your own order reference — no StackFill ids to persist beyond what you chose to keep:

curl "https://api.stackfill.com/v1/renders?external_order_id=YOUR-ORDER-REF" \
  -H "Authorization: Bearer stackfill_live_sk_…"

Each render carries a one-hour signed_url to the press-ready PDF. On a refund or cancellation, flip the fill's status through the fill-status endpoint — your adapter decides what a "credit note" or "cancel" means; the contract only needs the status change.

5 · Listing imagery (platform)

Pre-generate product and listing thumbnails from the same engine that prints the card, so what a shopper sees can't drift from what ships:

curl https://api.stackfill.com/v1/previews \
  -H "Authorization: Bearer stackfill_live_sk_…" \
  -H "Content-Type: application/json" \
  -d '{ "template_id": "tpl_…", "page": 0 }' --output listing.png

Store the PNG as the product image. Refresh it from the render.succeeded / batch.completed webhooks if the template changes.

The rule for adapters

An adapter is glue only: no rendering, no print rules, no StackFill semantics. If a platform genuinely needs something these five touchpoints don't cover, extend the contract — platform-neutrally, so every adapter benefits — rather than special-casing one storefront in StackFill core.