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.