Wix API Integration: Auth, Webhooks and the Catalog V3 Trap
Wix API integration explained: OAuth vs API keys, access tokens, the Catalog V1 vs V3 trap and webhooks that retry 12 times, with working code.
Long Nguyen
Fullstack Developer · AI Engineer · Researcher
What Wix API integration covers
A Wix API integration is code that reads or writes Wix data (products, orders, contacts, bookings) through Wix's REST API or JavaScript SDK, or reacts to Wix events through webhooks. It can run on the Wix site itself, on your own server, or inside an app installed on other people's sites.
Where the code runs decides how it authenticates, and that choice shapes everything else. Wix's own documentation splits it into five paths:
| Path | Where your code runs | Authentication |
|---|---|---|
| Extending a Wix site | On the Wix site | Host auth, applied automatically |
| Wix-managed app (CLI or Blocks) | Wix-hosted | Built in; Wix issues app-instance tokens for backend code |
| Self-managed app | Your server, installed on merchants' sites | OAuth |
| Headless project | Any stack you choose | OAuth for visitors and members; client credentials or an API key for admin work |
| External automation | Your server, script or CI job | API key |
Most "connect my Wix store to another system" projects land in the last two rows. Decide which one you are in before writing any code, because the credentials, token lifetimes and permission model all differ.
OAuth or API key: which Wix authentication to use
Wix supports OAuth and API keys for code that runs outside the Wix dashboard, editor or site. They are not interchangeable.
| OAuth | API key | |
|---|---|---|
| Best for | Apps installed on many sites; visitor, member and app identities | Server-to-server automation, external integrations, admin work across sites you manage |
| Lifetime | Access token valid for 4 hours | Valid until revoked or rotated in the Wix dashboard |
| Renewal | Visitor and member tokens: refresh token, 365 days by default, rotated on each use. App tokens: no refresh token, mint a new one | Not applicable |
| Issued by | Your app or headless project's OAuth client | An account owner or co-owner, in the dashboard |
| Permission model | Scopes granted to the app | Scopes plus site access (all sites in the account, or chosen sites only) |
| Third-party Wix apps | Required | Not available |
The rule of thumb for real projects: if the integration will be installed by more than one merchant or listed in the App Market, build an app and use OAuth. If it connects one business's Wix site to that same business's ERP, warehouse or marketplace tooling, a scoped API key is the shorter path, with a headless project's client credentials as the alternative when you only need admin access to that project's own site.
Treat an API key like an account password. Restrict it to the narrowest set of sites and scopes the integration needs, keep it in a secrets manager, and remember that site-level calls only work with keys generated from the account that owns the site. If you are doing this for a client, they will need to create the key, because only account owners and co-owners can.
How to get a Wix access token with client credentials
For admin operations in a headless project, the client credentials grant exchanges your OAuth client ID and secret for a short-lived token. Run it only in backend code. The secret is shown once when you generate it in Headless Settings, so store it immediately.
The token endpoint is https://www.wixapis.com/oauth2/token, and the response includes an expires_in of 14400 seconds. Do not request a new token on every call. Cache it and renew shortly before expiry:
let cached = { token: null, expiresAt: 0 };
async function getWixToken() {
const now = Date.now();
if (cached.token && now < cached.expiresAt - 5 * 60 * 1000) {
return cached.token;
}
const res = await fetch('https://www.wixapis.com/oauth2/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'client_credentials',
client_id: process.env.WIX_CLIENT_ID,
client_secret: process.env.WIX_CLIENT_SECRET
})
});
if (!res.ok) throw new Error('Wix token request failed: ' + res.status);
const data = await res.json();
cached = { token: data.access_token, expiresAt: now + data.expires_in * 1000 };
return cached.token;
}
async function wixPost(path, body) {
const token = await getWixToken();
const res = await fetch('https://www.wixapis.com' + path, {
method: 'POST',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
return res.json();
}
Note that the token goes into the Authorization header exactly as returned, with no extra prefix. An API key is sent in the same header. If a call returns 401, the token has most likely expired, so a renew-and-retry-once wrapper around wixPost is worth adding.
Wix Stores Catalog V1 vs V3: the trap in most integrations
This is where store integrations quietly break. Wix Stores has two product catalog APIs, and a given site is on one or the other. According to Wix's Catalog Versioning documentation, the two versions are not backwards compatible, all sites will eventually move to V3, and you should call the GetCatalogVersion endpoint at the beginning of each flow to know which API to use. Each version also fires its own webhooks, so an integration that must serve every site subscribes to both V1 and V3 events.
Wix's migration guide also states that there is no automatic migration path and that an app must support both versions to be installable by new and existing sites. The differences that matter in practice:
| Area | Catalog V1 | Catalog V3 |
|---|---|---|
| Products without options | Plain product | One default variant with an empty choices array; every purchasable entity is a variant |
| Variants in query results | Handled by V1 calls | Query and Search endpoints do not return variants; use the Read-Only Variants API |
| Stock | stock.quantity on the product |
Inventory Items API, searched by productId and/or variantId |
| Discounted price | price and discountedPrice |
With a discount: V1 price becomes compareAtPrice, V1 discountedPrice becomes actualPrice. Without: price becomes actualPrice |
| Options | Options with managedVariants true or false | Customizations API: options (create variants) and modifiers (do not) |
| Custom text fields | customTextFields |
Modifiers of type FREE_TEXT |
| Paging | numericId used for cursor paging |
Cursor paging built into Query and Search |
| Events | V1 webhooks | V3 webhooks with a changed payload structure |
The expensive mistake is writing against V3 because it is newer, then discovering a merchant's store is still on V1, or the reverse. Put an adapter between your business logic and Wix so that the version check happens once:
// One interface, two implementations. Business logic never sees the version.
const catalog = (await isCatalogV3(siteAuth))
? new CatalogV3(siteAuth)
: new CatalogV1(siteAuth);
const products = await catalog.listProducts();
const stock = await catalog.getStock(productId);
The V3 stock and variant split is the part that costs the most rework: what used to be one product read becomes a product query, a variants query and an inventory search. If you would rather not maintain two code paths yourself, this is the kind of work our Wix and ecommerce platform integration service covers.
How to handle Wix webhooks without losing events
Wix sends webhook data as a signed JSON Web Token in the request body, and you verify it with the public key from the Webhooks page of your app dashboard. The behavior below comes from Wix's webhooks documentation:
- Your endpoint must return HTTP 200 within 1250 ms, or Wix treats the delivery as failed.
- On failure, up to 12 additional attempts follow on a retry schedule.
- A resent webhook can arrive after a newer one that succeeded first time, so events can be delayed and out of order.
- Webhooks do not always carry the full entity, and Wix recommends designing for duplicate events.
- Sites on older major versions of your app do not receive newly added webhooks.
A 1250 ms window leaves no room for database writes or follow-up API calls. The handler should verify the signature, hand the event to a queue, and return 200. Nothing else:
import express from 'express';
import jwt from 'jsonwebtoken';
const app = express();
app.post('/webhooks/wix', express.text({ type: '*/*' }), function (req, res) {
let event;
try {
event = jwt.verify(req.body, process.env.WIX_APP_PUBLIC_KEY, {
algorithms: ['RS256']
});
} catch (err) {
return res.sendStatus(401);
}
queue.add(event); // your own job queue: parse and process later
res.sendStatus(200);
});
If you build with the Wix CLI, event extensions handle the payload for you, so this handler is only needed for self-hosted apps. The Logs tab on the Webhooks page of the app dashboard shows every delivery and is the first place to look when events seem to be missing.
A sync pattern that survives retries and outages
Because deliveries can repeat, arrive late or arrive out of order, a handler that says "stock changed, subtract 1" will drift. Wix's payload is a signal that something changed, not a ledger. This pattern holds up:
- Acknowledge fast. Verify, enqueue a small job (event type and entity ID), return 200.
- Re-read, do not trust the payload. The worker fetches the current entity from the Wix API, so the state it writes is always the latest one.
- Upsert on the Wix entity ID. Running the same job twice must produce the same result.
- Reconcile on a schedule. Wix itself recommends periodic API requests to confirm webhooks are accurate. A nightly query of recently changed products or orders catches anything a webhook missed.
- Cap concurrency and back off on HTTP 429. Bulk imports are where integrations trip over request limits.
Steps 2 and 4 are the difference between an integration that works in a demo and one that stays correct for months.
Wix API errors and what causes them
| Symptom | Likely cause | Fix |
|---|---|---|
| 401 on any REST call | OAuth access token expired (4-hour lifetime) | Mint or refresh a token and retry once |
| 401 on a Stores call: invalid authorization token, or Wix Stores not installed | Token does not belong to that site, or the site has no Wix Stores | Confirm the site has Stores installed and the token was issued for it |
| 403 with an API key | Key lacks the scope for that operation, or the target site is outside the key's site access | Adjust scopes and site access in the API Keys Manager |
| Site-level call fails with a valid key | Key was generated from a different account than the one that owns the site | Use a key from the owning account |
| Products have no variants after switching to V3 | V3 Query and Search do not return variants | Use the Read-Only Variants API |
| Integration works on one store, fails on another | The two stores are on different catalog versions | Call GetCatalogVersion first and branch |
| Some sites never get a new webhook | Site is on an older major version of your app | Ask merchants to update; check the Logs tab |
| Same event processed twice | Retry after a missed 200 or a timeout over 1250 ms | Deduplicate and make handlers idempotent |
Build it in-house or hire it out
A one-directional, read-only export from a single Wix store, owned by a team with a backend developer, is a reasonable in-house project. The picture changes when any of these is true:
- The sync is two-way (orders in, stock and tracking out) across marketplaces, an ERP or a warehouse system.
- More than one Wix store is involved, so you will meet both catalog versions.
- Nobody on your side wants to own webhook reliability, token handling and reconciliation long term.
Before you brief anyone, write down five answers: which direction data flows, which entities are involved (products, orders, customers), which catalog version each store is on, who owns the Wix account that can create an API key, and what should happen when a sync fails at 3 a.m. Those five answers are most of the scope.
If you want a second opinion or a build quote, request a free quote for your Wix integration with your store, the system you need to connect and the data involved. Pricing is set to scope.
FAQ
Frequently asked questions
Does Wix have a public API?
Yes. Wix offers a REST API for HTTP-based access to its business solutions and site data, such as Wix Stores products and orders, plus a JavaScript SDK and webhooks for events.
Do I need to build a Wix app to integrate my own store with another system?
Not necessarily. For server-to-server automation on sites you manage, an API key is designed for that use, and a headless project can use client credentials for admin calls. An app with OAuth is required when you distribute through the Wix App Market or install on other merchants' sites.
How long does a Wix access token last?
OAuth access tokens are valid for 4 hours. Visitor and member tokens come with a refresh token valid for 365 days by default, while app tokens from client credentials have no refresh token and must be minted again. API keys stay valid until you revoke or rotate them.
Can I use a Wix API key inside a third-party Wix app?
No. Wix API keys are not available for third-party apps. Apps must authenticate with OAuth.
What is the difference between Wix Stores Catalog V1 and V3?
They are two separate, non-backwards-compatible product catalog APIs. In V3 every product has at least one variant, stock lives in the Inventory Items API, options and modifiers are managed through the Customizations API, and V3 query endpoints do not return variants. Call GetCatalogVersion to see which one a site uses.
How fast must my endpoint answer a Wix webhook?
Within 1250 ms with an HTTP 200. If it does not, Wix retries up to 12 more times, which means duplicates and out-of-order events are normal and your handler should be idempotent.