Wcart/DevelopersSign in Free developer store

Developer docs

How to build, submit and get paid for themes and plugins on Wcart.

A plugin is any integration that runs on your own server. Nothing you write is deployed into Wcart; your plugin is described by a manifest (the settings it needs, your webhook and/or page URLs, the scopes it asks for) and talks to Wcart over OAuth, the scoped API and signed webhooks.
Simple pluginSettings + webhookReact to store events (an order is paid, a product changes) and act on them, configured by a few settings. Example: send order alerts to WhatsApp.
Full integrationYour own serviceYour own page and logic using the API to read and write store data. Example: sync products from a supplier, or ship orders through a courier.

Both use the same steps below; a full integration simply uses more of the API. (“Apps” on Wcart means mobile apps, which are separate.)

Steps

  1. Create the listingOn New listing, choose Plugin.
  2. DetailsDescription, screenshots, category and your price (in your currency).
  3. ManifestFill in the manifest builder on the Build step: name, settings fields, webhook URL and/or embedded page URL, and scopes. It is checked as you save.
  4. OAuth credentialsCreate them on the same step with your redirect URL (the fallback). You get a client_id and a client_secret; the secret is shown once. Your server needs both to accept installs — see How merchants get your plugin.
  5. Test installInstall it on your dev store from the Test install step. You get an access token (wca_…) once; your plugin calls the API with it. A test install may use every scope you request, and also shows the webhook signing secret once.
  6. Test orderDev stores cannot take real orders. Press Place test order next to the install (or POST /dev-stores/:storeId/test-order) to fire order.created at your webhook.
  7. SubmitSubmit a version. The reviewer sees the manifest, settings and URLs and decides which scopes to grant.
  8. LiveAfter approval Wcart prices it in its billing system; a priced plugin goes live for every store at once and appears on every merchant’s Plugins page.

The manifest

{
  "name": "Reviews Sync",
  "description": "Pull reviews from …",
  "settings_schema": [
    { "key": "api_key", "label": "API key", "type": "text", "required": true },
    { "key": "mode", "label": "Mode", "type": "select", "options": ["live", "test"] },
    { "key": "auto_publish", "label": "Publish automatically", "type": "boolean" }
  ],
  "embedded_url": "https://your-plugin.example/embed",
  "webhook_url": "https://your-plugin.example/webhooks/wcart",
  "scopes": ["orders:read"]
}
  • Field types: text, number, boolean, select (with options); up to 40 fields. Keys are lowercase letters, digits and _.
  • At least one of webhook_url and embedded_url. They must be public https URLs — Wcart calls them from its servers. To test a plugin running on your own machine, expose it with a tunnel (ngrok, cloudflared) and use that https address.
  • Also request the same scopes on the listing’s Details step: review grants from the listing’s requested scopes.
  • A manifest cannot name a React component.

Settings: what the merchant fills in

After installing, the merchant opens your plugin from Plugins → Manage in their Wcart admin and fills in the form your settings_schema describes (required fields are marked, selects and numbers are checked). Read the values from your server with the install’s token whenever you need them:

GET https://developer-api.wcart.io/app/v1-settings
Authorization: Bearer wca_…

→ { "store_id": "…", "settings": { "api_key": "…", "mode": "test" },
    "complete": true, "updated_at": "…", "form": [ … ] }

Defaults from your form fill any value the merchant has not set. complete is false until every required field is filled; show a “finish setup” message until then. On a test install the form comes from your saved draft, so you can try it before review; on a merchant install it is the approved version’s.

Your page inside the Wcart admin

If your manifest has an embedded_url, the merchant sees Open plugin and your page loads in a frame inside their admin. Wcart adds ?wcart_embed=…&store_id=…&plugin=…. Never trust store_id from the URL: verify the embed token from your server first:

POST https://developer-api.wcart.io/oauth/embed/verify
{ "client_id": "wcapp_…", "client_secret": "wcsec_…", "embed_token": "wcemb_…" }

→ { "store_id": "…", "plugin": "…", "install_id": "…", "scopes": [...],
    "user_email": "…", "expires_at": "…" }

The token is valid for 10 minutes; start your own session once it is verified. Your page must allow being framed by the Wcart admin (do not send X-Frame-Options: DENY, or allow frame-ancestors for admin.wcart.io). The merchant can also open it in a new tab.

Scopes

Request scopes in the manifest; review decides which are granted, and an install only ever holds those. A test install on your own dev store gets every scope you request. :write includes :read. Sensitive scopes (money, personal data, outbound email) need a reason in your review notes.

Catalogue
  • products:read — Read products, categories, brands and tags
  • products:write — Create and update products, categories, brands, tags and attributes
  • inventory:read — Read stock levels and pickup locations
  • inventory:write — Adjust stock
Orders
  • orders:read — Read orders and their line items
  • fulfillments:write — Add shipment tracking to an order
  • payments:read — Read payment transactions of orders
  • payments:write — Mark an order as paid (payment-gateway plugins) · sensitive
Customers
  • customers:read — Read customer records · sensitive
  • customers:write — Create guest customer records · sensitive
Store
  • store:read — Read store address, currencies, tax settings and locations
Store plugins
  • reviews:read — Read product reviews and rating summaries · data exists only on stores using Ratings & Reviews
  • barcodes:read — Look up a product by barcode · data exists only on stores using Product Barcode
  • memberships:read — Read a customer’s membership benefits and product access · data exists only on stores using Memberships
  • wallet:write — Credit, debit and reverse customer store credit · sensitive · data exists only on stores using Wallet
  • reward_points:write — Award, redeem and reverse reward points · sensitive · data exists only on stores using Reward Points
  • gift_cards:write — Issue gift cards · sensitive · data exists only on stores using Gift Cards
Messaging
  • notifications:send — Send an email to a customer through the store’s mail settings · sensitive
Events
  • webhooks:manage — Receive the store’s events (orders, products) as signed webhooks

Calling Wcart: the API gateway

Send the install’s token as a bearer token to https://developer-api.wcart.io. Every route, with the scope it needs:

GET  /app/v1-whoami                           → your plugin, store and scopes
GET /app/v1/products                          products:read       List published products (?page, limit, q, category)
GET /app/v1/products/by-ids                   products:read       Read products by id (?ids=a,b)
GET /app/v1/products/slug/:slug               products:read       Read one published product by slug
GET /app/v1/categories                        products:read       List categories
GET /app/v1/brands                            products:read       List brands
GET /app/v1/tags                              products:read       List tags
POST /app/v1/products                         products:write      Create a product (product_name, sku, price, sale_price, quantity, status)
PUT /app/v1/products/:id                      products:write      Update a product (any of the create fields)
POST /app/v1/categories                       products:write      Create a category (name)
POST /app/v1/brands                           products:write      Create a brand (name)
POST /app/v1/tags                             products:write      Create a tag (name)
POST /app/v1/attributes                       products:write      Create an attribute (name)
GET /app/v1/inventory/stock                   inventory:read      Stock for products (?ids=a,b)
GET /app/v1/inventory/pickup-locations        inventory:read      Store pickup locations
POST /app/v1/inventory/adjust                 inventory:write     Adjust stock (product_id, delta)
GET /app/v1/orders/by-users                   orders:read         Orders of customers (?user_ids=a,b)
GET /app/v1/orders/:id                        orders:read         Read one order with its items (each item gets product_name)
POST /app/v1/orders/:id/tracking              fulfillments:write  Add tracking (carrier, tracking_number, tracking_url)
GET /app/v1/payments/by-orders                payments:read       Transactions of orders (?order_ids=a,b)
GET /app/v1/payments/by-reference             payments:read       Transaction by gateway reference (?reference=)
POST /app/v1/orders/:id/paid                  payments:write      Mark an order paid (transaction reference)
GET /app/v1/customers/search                  customers:read      Find customers (?q=email or phone)
GET /app/v1/customers/by-ids                  customers:read      Read customers by id (?ids=a,b)
POST /app/v1/customers/guest                  customers:write     Find or create a guest customer (email, name, phone)
GET /app/v1/store/address                     store:read          Store address
GET /app/v1/store/currency                    store:read          Default currency
GET /app/v1/store/tax                         store:read          Tax settings
GET /app/v1/store/locations                   store:read          Store locations
GET /app/v1/store/currencies                  store:read          All enabled currencies with rates
GET /app/v1/reviews                           reviews:read        Approved reviews of a product (?product_id=)
GET /app/v1/reviews/summary                   reviews:read        Rating summary of a product (?product_id=)
GET /app/v1/barcodes/lookup                   barcodes:read       Product for a barcode (?code=)
GET /app/v1/memberships/member-benefits       memberships:read    Benefits of a customer (?customer_id=)
GET /app/v1/memberships/product-access        memberships:read    Can a customer see a product (?customer_id, product_id)
POST /app/v1/wallet/credit                    wallet:write        Credit store credit (customer_id, amount, idempotency_key)
POST /app/v1/wallet/debit                     wallet:write        Debit store credit (customer_id, amount, idempotency_key)
POST /app/v1/wallet/reverse                   wallet:write        Reverse a wallet entry (idempotency_key)
POST /app/v1/rewards/earn                     reward_points:write Award points
POST /app/v1/rewards/redeem                   reward_points:write Redeem points
POST /app/v1/rewards/reverse                  reward_points:write Reverse a points entry
POST /app/v1/gift-cards                       gift_cards:write    Issue a gift card (amount, recipient_email)
POST /app/v1/notifications/email              notifications:send  Email a customer (to, subject, body, heading)

Authorization: Bearer wca_…

Lists (/products, /categories…) return what the storefront shows; by-id reads see everything in the store. Creating a product needs product_name and a unique sku. Order line items carry productid — read names with /products/by-ids.

The store comes from the token, never the request. Anything not listed answers 404 NOT_ALLOWED; a call without its scope answers 403 MISSING_SCOPE.

How merchants get your plugin

Merchants never visit this portal. Everything happens in their Wcart admin → Plugins, where your plugin is listed with Wcart’s own (with “by your organisation” under its name). Wcart bills them and pays you.

  1. DiscoverIn the admin’s Plugins page, or in the marketplace — whose button sends them to their admin.
  2. BuyA paid plugin shows Buy Now · price and goes through Wcart’s checkout. A free one, or one included in the store’s plan, skips this.
  3. ApproveThey press Activate, see the access review granted you, and press Approve & activate. Nothing is granted before that.
  4. Wcart tells your serverWcart POSTs a signed app.installed event to your install URL (manifest install_url, else your webhook_url) with a one-time code.
  5. You exchange the codeYour server swaps it (single use, valid 10 minutes) for that store’s token — before answering 200, so the merchant sees “connected” straight away.
  6. Set upThe admin opens your plugin’s page: your settings form and your embedded page. Anything you need from the merchant (signing in to Google, an API key of theirs) happens there, never on a separate site.
POST <your install URL>                         ← from Wcart
X-WCart-Event: app.installed
X-WCart-Timestamp: 1791358411629                (unix ms)
X-WCart-Signature: <hex HMAC-SHA256 of "<timestamp>.<raw body>">
{ "id": "evt_…", "event": "app.installed",
  "payload": { "store_id": "dep_…", "plugin": "<your slug>", "install_id": "…",
               "code": "wccode_…", "scopes": [...], "mode": "live" } }

signing key = SHA-256 (hex) of YOUR client secret

POST https://developer-api.wcart.io/oauth/token                     → to Wcart
{ "client_id": "wcapp_…", "client_secret": "wcsec_…", "code": "wccode_…" }
→ { "access_token": "wca_…", "store_id": "dep_…", "scopes": [...],
    "webhook_signing_secret": "whsec_…" }

If your server cannot be reached, Wcart retries for about 4 minutes and offers the merchant a “Finish setup” link to your redirect URL (?code=…&store_id=…&plugin=…) — keep that route working as the fallback. On deactivation Wcart revokes the token, removes the webhook subscription and POSTs a signed app.uninstalled ({ store_id, plugin, install_id }) to the same URL.

Activation rules

SituationWhat happens
Your organisation is not approved yetYou cannot create listings. Set up your dev store and payout details meanwhile.
Your own dev storeTest install from the listing’s Test install step — free, every scope you request, token shown once. Other developers’ plugins cannot be installed there.
Paid plugin, store has not bought itActivate is refused (402 PURCHASE_REQUIRED); the merchant is shown Buy Now.
Bought, included in the plan, or granted free by WcartActivate works. You earn in every case (see Earnings).
Merchant deactivatesToken revoked at once, app.uninstalled sent. Re-activating needs no new purchase and issues a new token.
Store expires or is suspendedYour API calls for it fail; keep the record, it may come back.
You publish a new versionExisting installs keep working. Asking for more scopes needs review, and merchants approve the new access.

Only the store’s owner can activate or deactivate a plugin.

Running one plugin for many stores

One deployment of your plugin serves every store that activates it. Key everything by store_id.

Keep per storeWhere it comes from
store_idapp.installed payload / token exchange
access_token (encrypted at rest)token exchange — for /app/v1/… calls
webhook_signing_secrettoken exchange — verifies that store’s order/product webhooks
granted scopes, status (active / uninstalled)token exchange; app.uninstalled
the merchant’s settingsGET /app/v1-settings (they edit them in their admin — do not copy them into your own form)
  • Which store sent this webhook? The one whose signing secret verifies the signature (or the store_id in the payload, after verifying).
  • Which store is viewing my page? Verify the wcart_embed token with POST /oauth/embed/verify — it returns the store. Never trust a store_id in the URL alone.
  • Calling the API: use that store’s token. The store comes from the token, so one store’s token can never touch another store.
  • Idempotency: install, webhook and uninstall events can arrive twice; use the event id / install_id and upsert.
  • Re-install: a new app.installed for a store you know replaces its token and secret — overwrite, don’t duplicate.
  • Isolation: never mix two stores’ data in one query, file or cache key; log the store_id with every action.

The reference plugin Sheets Sync does all of this in one small file — ask Wcart for the source.

Webhooks

With a webhook URL, each install is subscribed to Wcart’s events (e.g. order.paid), signed with HMAC-SHA256 using the install’s signing secret, retried with backoff and audited. Verify the signature before trusting a payload. Uninstalling removes the subscription.

Guidelines

  • Ask only for the scopes you use; reviewers grant the minimum.
  • Keep the client secret and tokens on your server only, never in a browser or app bundle.
  • Respond to webhooks quickly (2xx) and do slow work afterwards; handle the same event arriving twice.
  • Test every flow on your dev store before submitting, including uninstall and reinstall.
  • Answer app.installed only after the code exchange succeeds, within 8 seconds; reject a bad signature or a timestamp older than 5 minutes.