01 / content master
Salsify owns the product record
Titles, specs, enriched copy, digital assets, taxonomy, locale values. The authoritative source the merchandising team works in.
Integration / Makro Middleware
Salsify is your product content master. Shopify is your storefront, and your system of record for orders. We build custom Salsify to Shopify Plus middleware: attribute-to-metafield mapping, variants, markets and B2B.
The real problem
Wrong variant values, stale images, untranslated markets, codes shipped instead of names. Silent drift no one catches until a buyer does.
01 / content master
Titles, specs, enriched copy, digital assets, taxonomy, locale values. The authoritative source the merchandising team works in.
02 / storefront
Variants, metafields, Markets, B2B price lists, and the order. Inventory and orders are not Salsify's job.
03 / the gap
Wrong variant values, stale images, untranslated markets, category codes shipped instead of names. Silent drift no one catches until a buyer does.
We built a product around that exact gap. Salsify plugs into it as a first-class, path-mapped adapter on the platform that runs in production for NetSuite.
Architecture
Product content is read out of Salsify and published to Shopify. The sync is one way. There is no order or inventory write-back to Salsify, because Shopify is the system of record for orders and storefront state.
Content master
read · enriched · governed
The platform
map · resolve · reconcile
The storefront
orders live here, not in Salsify
/api/v1/orgs/{org-id}/...; export runs drop the v1. The CRUD API is for precision reads, never the full-catalog sync.Salsify → MKAuthorization: Bearer, non-expiring unless regenerated, with the s-… org id pinned in every URL as the hard tenant boundary.handshakeTHROTTLED.MK → ShopifyThe console
Every publish, every failure, every reconciliation is visible. This is the same Makro Middleware console your team would watch in production. Move through the views.
| Time | Entity | Error class | Attempts | Next | Action |
|---|---|---|---|---|---|
| 08:13:04 | product · SKU-AURELIA-3 | PERMANENT · validation | 3 / 3 | held | Inspect |
| 07:55:22 | digital_asset · IMG-44120 | TRANSIENT · timeout | 2 / 5 | 09:50 | Retry now |
| 06:40:11 | export_run · meridian-all | RATE_LIMIT | 1 / 5 | paced | Inspect |
Every record carries three identities at once: its salsify:system_id, a canonical id, and its Shopify gid. The middleware reads this row before every write and decides create, update, or skip.
Writes key on the immutable salsify:system_id plus salsify:version, so a retried or coalesced webhook publishes to Shopify exactly once. No duplicate products, no double-pushed variants.
Inherited values are the trap: a child's version does not bump when a parent value changes, so the platform watches the parent and re-resolves the family, not just the leaf.
entity key map · every id matched before write
Inside the platform
The mapping problems and failure modes no vendor page admits are handled by named, tested subsystems. This is what makes the Salsify path mapped, not theoretical. None of them is a setting you toggle.
01 / 10
When a record fails
A Salsify → Shopify publish is classified before it is retried, and nothing disappears into a log file. Colour carries the verdict: amber is captured and sorted, teal retries, red is held, green replays.
A Salsify → Shopify publish fails: a validation error, a Salsify 429, or a Shopify THROTTLED. Instead of disappearing into a log, it enters the pipeline.
Every failure is sorted before anything is retried, so a bad record never loops forever.
classifiedA Salsify 429 or a Shopify throttle is paced against both rate models and retried with backoff, automatically, until it clears.
A bad metafield type or an invalid variant set routes straight to the dead-letter queue for review. It is never retried blindly or lost.
held for reviewFix the cause, replay from the queue. A daily reconciliation pass catches anything the live publish path missed.
replayed · reconciledWhere off-the-shelf sync breaks
The native channel and most iPaaS templates are competent on the happy path. They break on the mapping surface no vendor page admits: the variant cap, locale-keyed values, category resolution, the inheritance trap, asset lifecycle, B2B price lists, headless, and reconciling two incompatible rate limits.
Shopify caps each product at 100 variants and three option types. Large or complex catalogs need metafield-based variant modeling the native push does not solve on its own.
→ metafield-modeled variants, family-aware collapseA localized Salsify value is a nested object keyed by locale, not a flat string, and only on localizable properties. Treat it as a scalar and you ship a stringified object to the storefront.
→ iterate the locale map → Shopify Markets / Translate & AdaptA category is a code referencing a value tree, not free text. Ship the raw code and the storefront shows gibberish.
→ resolve the code to salsify:name plus its ancestor pathA child variant's salsify:version does not increment when an inherited value changes on the parent. A delta scheme keyed on the variant alone silently misses parent-driven changes.
→ watch the parent, re-resolve and re-publish the familyAssets are separate records with a lifecycle, referenced by id, not inline blobs. Push only status: completed, detect change by etag, honor the featured-asset flag.
→ push salsify:url, etag change-detection, profile asset = featuredSalsify has no native currency type, and Shopify B2B price lists and catalog segments are a Shopify-side construct the native push does not drive. Flattening the right values per company is custom-middleware work.
→ per-company price lists flattened on the Shopify sideA connector that handles basic sync often fails when extended to Storefront API queries for a headless build. That path needs orchestration the native channel does not provide.
→ feed the metafield schema a headless front end actually queriesSalsify's hourly fixed window and Shopify's per-second leaky bucket are two incompatible throttling models the native channel reconciles nowhere. A token-aware pacer against both is the only way to run full-catalog syncs cleanly.
→ one scheduler pacing against both ceilingsThe authority table
Every reality below is verified against a primary Salsify or Shopify source, a named vendor, or our own first-party work against a live Salsify org. This is the credibility engine, not the sales pitch.
429 over the limit. Against a real ~36,654-product / ~66,357-asset org, one full crawl burns the entire budget several times over. Full syncs go through export feeds.salsify:url and only assets with status: completed, use salsify:etag to detect a changed image, and honor the profile-asset id for the featured image. A real ~66,357-asset volume is exactly what makes naive per-asset GET loops untenable.The decision guide
There are three ways to connect Salsify to Shopify: the native Salsify direct connector or channel feed, an iPaaS platform like Patchworks or Alumio, and a custom-built integration maintained by a Shopify Plus development agency. The right choice depends on catalog complexity, B2B needs, and whether your storefront is headless.
Option 01
Salsify's own Plus Certified channel feed. Included with Salsify, strong at one-way content publishing and source-side governance.
Right for straightforward one-way content publishing where the catalog fits the native model and there is no headless or B2B layer.
Option 02
Subscription middleware on a shared platform. Adds monitoring, logging, retries, and multi-system orchestration across templated connectors.
Right for monitored multi-system orchestration where the mapping logic fits a fixed template and retries are the main need.
Option 03 our build
Built around your exact product model and B2B logic, owned by you, operated with a support agreement. Where Plus, multi-market, and B2B brands end up.
Right for Shopify B2B price lists, a headless Storefront API front end, complex variant modeling beyond the 100-variant cap, or multi-market routing a fixed-mapping connector cannot model.
How long it takes
A Salsify ⇄ Shopify Plus build lands in one of three bands depending on catalog complexity, multi-market and B2B requirements, and how many of the break points above are in play. The week ranges below are market scoping context.
Band 01 · foundation
single store · standard catalog
Band 02 · standard
multi-market · richer mapping
Band 03 · full build
B2B · headless · complex variants
We price a Salsify integration as an engineering engagement: a build with a support agreement, not a per-record meter or a platform seat that renews forever. For market context, comparable custom builds in this space run roughly $20K to $40K for a basic publish, $45K to $80K for a standard multi-market build, and $90K to $160K for a full B2B and headless build. Your exact figure is scoped to the band and the break points in play, and we put it in writing before anything starts.
Security posture
Auth runs through a dedicated Salsify API Key service user, presented as Authorization: Bearer, non-expiring unless regenerated, with the s-… org id pinned in every URL as the hard tenant boundary.
API Key service user · scopedEvery Salsify webhook is verified by X-Salsify-Signature-V1 (asymmetric X.509 plus SHA-256), with the cert host pinned to exactly webhooks-auth.salsify.com and a timestamp check to block replay, before it is trusted.
X-Salsify-Signature-V1 · replay-safeSalsify and Shopify secrets are held as encrypted fields, isolated per tenant and rotatable without downtime. No credential sits in plaintext, anywhere, ever.
AES-256-GCM · per-tenantAn append-only audit trail logs every publish, transformation and error, source-tagged. Salsify's own posture (SOC 2 Type II, ISO 27001:2013, TLS 1.2+, AES-256 at rest) sits behind it.
append-only · source-taggedFrequently asked questions
The details that separate a team that read the docs from a team that shipped against them. Open any one.
salsify:parent_id. We collapse leaf products into Shopify variants and lift shared parent values onto the Shopify product. Property salsify:data_type drives the Shopify target: string to field or metafield, enumerated to option or metaobject, rich text to body HTML, digital asset or link to media or URL. salsify:attribute_group scopes which properties to pull, and custom attributes are any non-salsify:-prefixed key, selectable by external id in the export.salsify:version does not increment when an inherited value changes on the parent. A delta scheme keyed only on the variant's version silently misses parent-driven changes, which is exactly how naive connectors drift out of sync. We watch the parent, re-resolve inherited values, and re-publish the whole variant family rather than the leaf alone.{"en-US": …, "pt-PT": …}, and only on localizable properties. We iterate the locale map and route each value to the right Shopify Markets or Translate & Adapt context, rather than assuming a scalar string and shipping the wrong language to the storefront.The framework underneath
Every system sits behind the same contract: authenticate, fetch master data, push, validate, report. The sync engine, mapping layer, error handling and Shopify integration never change when a new system plugs in. NetSuite runs on that framework in production today. Salsify is a registered target on the same framework, its path mapped to exports, REST reads, webhooks and Bearer auth.
We say this plainly: NetSuite is the adapter marked in production. The Salsify path is fully mapped and first-party verified against a live org, not a live two-way production deployment. That distinction is exactly what your architect can verify, which is why we lead with it.
Proof and outcomes
First-party · production read-only Salsify integrationWe operate a production read-only Salsify integration against a live org running STORIS ERP → Salsify → Shopify, across a real catalog of ~36,654 products and ~66,357 digital assets. It independently confirms Salsify's base URL, the salsify:-namespaced field shapes, and the x-ratelimit-* headers. This is what makes Salsify path mapped, the mechanics on this page are verified against a live org, not just the docs.
Automated tests across 139 suites, run on every change. No code reaches your production data without passing them.
Coverage gate enforced before any deploy.
End-to-end browser tests, across Chromium, Firefox and WebKit.
Failure classes, each with its own pre-decided policy.
Credentials stored in plaintext, anywhere, ever.
By analogy, not as a Salsify reference: we built a unified account page integrating Clarius' NetSuite ERP, Salesforce CRM and Shopify Plus, with Shopify Flow automation feeding order data into NetSuite, which eliminated manual order entry and reduced operational costs by over $100,000 a year. Clarius did not involve Salsify or PIM; it is borrowed evidence that we unify enterprise systems into Shopify Plus. The test and coverage numbers above are the platform standard, measured on the NetSuite-in-production platform, not on a live Salsify deployment.
A real conversation about your Salsify org, your catalog, and the mapping break points you already know are coming.